Ansible (ansible.com) is a tool of choice to automate anything you’d like for your servers. And guess what? “- What?” It’s written in Python. Hooray!
It took me waaay too long to realize it was THE tool I needed. But there are new things everyday in our industry, keeping up is hard. At Europython2017, I had the opportunity to witness how well Ansible spread within our community. Everyone seems to love it and I’m late to the party.
What you need to know right now : Ansible is dead simple to use and can do magic for you. Most of the time, you don’t have to hack a single thing. It’s like the phone marketplaces , if you want something, there are certainly apps for that. Hum, sorry, here, it’s called a module but it’s all the same.
On the Ansible website, you may notice something called “Ansible Tower” but let’s stick to the free part and the pure Ansible part.
Let’s take together a dead simple example to discover Ansible: nginx installation on a remote server.
Install
Before anything which may seem witty, we need to install it! You can see the official installation documentation for a more detailled approach. As I’m working on an ubuntu machine, the following examples assume you have a similar setup or you have the ability to “translate” the commands.
Via apt ubuntu :
sudo apt-get install software-properties-common
sudo apt-add-repository ppa:ansible/ansible
sudo apt-get update
sudo apt-get install ansible
That’s it.
First blood
Files we’re going to create can live almost anywhere on your machine. Let’s suppose you store them all in one place, maybe in your project root repository or in a folder inside it.
Let’s create a file with server lists called hosts
:
[web]
192.168.33.10
192.168.33.11
192.168.33.12
[database]
192.168.33.13
192.168.33.14
It allows us to run recipes for groups and list servers within each group. Let’s start with a more simple “hosts” file containing only the address of one server of mine:
[web]
192.168.33.10
Ansible is about creating recipes to do stuff. It’s like cooking but with code.
A recipe is called a playbook
and is written with yaml
- name: Playbook to install nginx
hosts: web
remote_user: root
tasks:
- name: Install git
(...)
remote_user
being the user we want to use.
From here, we can define some tasks to perform. For example, we may want to install a few dependencies before nginx on our machine.
Ansible provide modules to ease such tasks. Modules can be found online on their website. This is one of the most important page of the Ansible website.
To install git for example, we can use the apt
module which need name
and update-cache
as arguments. While we’re on it, let’s also add vim
on our server to trick everyone because you know, nobody knows how to exit vim.
- name: Playbook to install nginx
hosts: web
remote_user: root
tasks:
- name: Install git
apt: name=git update_cache=yes
- name: Install Vim
apt: name=vim
Here, we have our first playbook. We can execute it with the following command:
ansible-playbook -i hosts playbook.yml
Output should be similar to:
PLAY [Playbook to install nginx] ***********************************************
TASK [setup] ***************************************************
ok: [192.168.33.10]
TASK [Install git] ***************************************************
changed: [192.168.33.10]
TASK [Install Vim] ***************************************************
changed: [192.168.33.10]
PLAY RECAP *******************************************************************
192.168.33.10 : ok=3 changed=2 unreachable=0 failed=0
If we execute this playbook again, Ansible will check first if packages are installed and it will be a lot faster.
Let’s go deeper
We can save a few lines and installed our dependencies all in once. Let’s add some moar packages in this step.
- name: Playbook to install nginx
hosts: web
remote_user: root
tasks:
- name: Install deps
apt: name= update_cache=yes state=latest
with_items:
- vim
- htop
- git
Hooray. I already like this. But you’re not going to impress a lot of people with this.
Deeeeeeeper: new user and ssh
Let’s add a task for user creation and let’s copy our ssh key to ease our server connections. We will need new modules: user
and authorized_key
.
- name: Playbook to install nginx
hosts: web
remote_user: root
tasks:
- name: Install deps
apt: name= update_cache=yes state=latest
with_items:
- vim
- htop
- git
- name: User creation
user: name=adrien comment='My new user' shell=/usr/bin/bash
- name: Adding SSH key
authorized_key: user=adrien key=""
Modules are well documented and these lines come directly from the documentation. No panic if you feel a bit lost: stick to the Ansible documentation.
After executing our playbook, we may try to connect to the server with the adrien
user and we should indeed connect without a hassle.
Are you even sorry?
We could be happy about what we did but have you paid attention carefully? We have specified multiple times our “user” variable (adrien). We are baaaaaad people. Let’s keep it DRY!
A tiny refactor to the rescue:
- name: Playbook to install nginx
hosts: web
remote_user: root
vars:
user: adrien
tasks:
- name: Install deps
apt: name= update_cache=yes state=latest
with_items:
- vim
- htop
- git
- name: User creation
user: name= comment='My new user' shell=/usr/bin/bash
- name: Adding ssh key
authorized_key: user= key=""
I feel so much better now. Our recipe is easily reusable by anyone else which does’t wan’t to be called like me “adrien”. Only thing to do, update the variables part.
If you’re a bit into testing, you probably already wonder what would happen without the variable user being declared? Well, you can anticipate this in your recipe and skipp some part when irrelevant.
- name: Playbook to install nginx
hosts: web
remote_user: root
vars:
tasks:
- name: Install deps
apt: name= update_cache=yes state=latest
with_items:
- vim
- htop
- git
- name: User creation
when: user is defined
user: name= comment='My new user' shell=/usr/bin/bash
- name: Adding ssh key
when: user is defined
authorized_key: user= key=""
If you execute the above playbook, both tasks “User creation” and “Adding ssk key” will be ignored ("skipping"
on the ouput of Ansible).
Promote everyone
Our noob user adrien
does not have superuser rights, let’s add him to the “sudoers” group. In order to perform this, we’re going to create a template and use our first jinja file!
Create templates/sudoers.j2
:
ALL=(ALL:ALL) NOPASSWSD: ALL
And within our playbook :
- name: Playbook to install nginx
hosts: web
remote_user: root
vars:
tasks:
- name: Install deps
apt: name= update_cache=yes state=latest
with_items:
- vim
- htop
- git
- name: User creation
when: user is defined
user: name= comment='My new user' shell=/usr/bin/bash
- name: Adding ssh key
when: user is defined
authorized_key: user= key=""
- name: "" becomes a sudoer
when: user is defined
template: src=templates/sudoers.j2 dest=/etc/suoders.d/-sudoer validate='visudo -cf %s'
Hooray, a step closer to our initial goal.
Roles, everywhere
The next step to get nginx is to install and setup nginx. This step seems obvious. We want to install nginx via ansible. It is more than obvious that at some point in our playbook tasks, we’re going to need to install nginx. We could add a new tasks right away but let’s take a step back and look at what we have done so far.
We are still very far from our goal and yet the playbook is getting longer and longer. What about reusability?
Ansible provides the concept of roles
. One role is responsible for a specific task and can be reused for different playbook.
Let’s create a new folder roles/utils/tasks
and a file main.yml
in it. Copy/paste the content of your first task in it:
- name: Install deps
apt: name= update_cache=yes state=latest
with_items:
- vim
- htop
- git
Let’s create another repository for our user role roles/user/tasks
and the according file main.yml
:
- name: User creation
when: user is defined
user: name= comment='My new user' shell=/usr/bin/bash
- name: Adding ssh key
when: user is defined
authorized_key: user= key=""
- name: "" becomes a sudoer
when: user is defined
template: src=templates/sudoers.j2 dest=/etc/suoders.d/-sudoer validate='visudo -cf %s'
Our playbook becomes:
- name: Playbook to install nginx
hosts: web
remote_user: root
vars:
roles:
- utils
- user
You are probably wondering if it makes sense to write such stupid roles. We’re not the first users of Ansible and it must have been done a Jeelion times. That’s true. That’s why you can go waste a few precious minutes on galaxy.ansible.com to discover roles written by the community.
Keep in mind that these role are to be used at your own risk. Ensure you understand the content of the roles you want to copy/use. If it can be a bit dangerous to reuse direclty roles from there, it can at least be a great source of inspiration.
nginx role, finally!!!
Now that our playbook is split into differents roles, let’s add the nginx one:
- name: Playbook to install nginx
hosts: web
remote_user: root
vars:
roles:
- utils
- user
- nginx
Create roles/nginx/tasks/main.yml
:
- name: Install
become: yes
apt: name=nginx state=latest
- name: Start
become: yes
service: name=nginx state=started enabled=true
become: yes
allows us to ensure the command try to be executed as an admin.
Executing this playbook, you should get nginx install on your machine. If you go to your server URL, you should get the nginx homepage.
It is quite common to hide this page. Let’s delete the default config and end our ansible discovery for today.
What we need is to delete some files and restart nginx. My first guess is that we’re going to need 2 tasks:
- One to delete the default config
- One to restart nginx
If you think about it, every modification to the nginx configuration may require a nginx restart. I’m so lazy I won’t let a chance to my future self to write twice the same task. It allows me to introduce you the last bit of Ansible before I let you go: handlers
!!
Every task can notify a handler. Handlers will be executed once all tasks have been completed if they have been notified. In our case, it ensures we specify the restart command once, we notify the handler, and nginx will restart only once after all the changes we need to make on our nginx conf.
Let’s rewrite our nginx role:
- name: Install
become: yes
apt: name=nginx state=latest
- name: Start
become: yes
service: name=nginx state=started enabled=true
- name: Delete defaut config
become: yes
file: path=/etc/nginx/sites-enabled/default state=absent
notify: reload nginx
Now we need the file roles/nginx/handlers/main.yml
:
- name: reload nginx
action: service name=nginx state=reloaded
Now, nginx homepage shouldn’t be visible anymore.
We could carry on all night and setup everything we need to replicate a production server but I need to go to sleep. We’ve seen everything you need to know to rock with Ansible: tasks, roles, templates and handlers. Have fun, happy coding!
Cheers