for DevOps
Machine provisioning and application deployment with ease.
ansible presentation 06/15 @ Web-Dev-BBQ #4 Stuttgart/Germany
Markus Wanjura
Head of IT & Development
@ develop4edu GmbH Stuttgart
XING: xing.to/wanjura GitHub: git.io/FGqg
Felix Peters
Application Developer
@ develop4edu GmbH Stuttgart
XING: xing.to/flxpeters GitHub: git.io/vIlcG
We are just looking for
frontend freelancers!
Apply now!
- What Is Ansible?
- The Basics
- Provisioning
- Application Deployment
- Tools & Hints
What's your poison?
Puppet, Chef, Salt, Cobbler, CFEngine, YADT, JuJu, BASH, Ant, Phing, Capistrano, Deployer, Drush, Surf, Docker, Rocket...?
Or all of them?
You think DevOps and the tools are complicated?
You hate it?
You're right!
Are you serious?
No DevOps for you?
You know YAML?
A bit of BASH?
Damn!
You are a freaking awesome DevOps pro!!!
So what is ansible?
So what is ansible?
- An open-source software for configuring and managing computers.
- Founded by Michael DeHaan
- First release February 20, 2012 - 3 years ago
- Written in Python
- It runs over SSH or PowerShell
- It was named "ansible" after the fictional instantaneous hyperspace communication system featured in Orson Scott Card's "Ender's Game"
Michael DeHaan's motivation?
"I wrote ansible because none of the existing tools fit my brain.
I wanted a tool that I could not use for 6 months, come back later, and still remember how it worked."
Design goals
Minimal in nature
Secure
Only OpenSSH is required.
Highly reliable
Low learning curve
Ok let's have some fun!
Ansible Basics
... is worth a thousand words.
A basic playbook.
Installs an Apache, configures and restarts it. On 1 or 1000 Servers...
- hosts: webservers
vars:
http_port: 80
tasks:
- name: ensure apache is at the latest version
yum: pkg=httpd state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
- name: ensure apache is running (and enable it at boot)
service: name=httpd state=started enabled=yes
But let's start step by step
Install ansible
Python only
$ sudo pip install ansible
On CentOS with EPEL repo enabled
$ sudo yum -y install ansible
On Debian or Ubuntu with ansible ppa
$ sudo apt-get install -y ansible
Inventories
./inventories/mycluster
Content of the inventory file
[mycluster]
node-01.internal.acme.com
node-02.internal.acme.com
[webservers]
node-[01:1000].internal.acme.com
[databases]
db-[a:f].internal.acme.com
[myspecialpony]
pony ansible_ssh_host=192.168.1.50 ansible_ssh_user=vagrant
Ad-Hoc ansible commands
Basic ping
$ ansible -i inventories/mycluster webservers -m ping
Get free memory
$ ansible -i inventories/mycluster databases -a 'free -m'
Playbooks
./site.yml
- hosts: webservers
vars:
http_port: 80
tasks:
- name: ensure apache is at the latest version
yum: pkg=httpd state=latest
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
- name: ensure apache is running (and enable it at boot)
service: name=httpd state=started enabled=yes
Roles
Installs an Apache and PHP
- hosts: webservers
vars:
http_port: 80
hostname: foobar.tld
max_allowed_memory: 128M
roles:
- { role: apache, tags: apache }
- { role: geerlingguy.php, tags: php }
Your apache tasks are now located here
./roles/apache/tasks/main.yml
Shared roles
- hosts: webservers
roles:
- { role: apache, tags: apache }
- { role: geerlingguy.php, tags: php }
Install a shared role globally
$ ansible-galaxy install geerlingguy.php
- downloading role 'php', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php/archive/1.6.1.tar.gz
- extracting geerlingguy.php to /etc/ansible/roles/geerlingguy.php
Shared roles
./ansible.cfg
[defaults]
roles_path = ./vendor:./roles
Install a shared role locally
$ ansible-galaxy install geerlingguy.php
- downloading role 'php', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-php/archive/1.6.1.tar.gz
- extracting geerlingguy.php to ./vendor/geerlingguy.php
Variables
The vars_files
- hosts: webservers
vars_files:
- vars/main.yml
roles:
- { role: apache, tags: apache }
- { role: geerlingguy.php, tags: php }
Your vars are now located here
./roles/apache/vars/main.yml
Folder structure
├── ansible.cfg
├── site.yml
├── host_vars
│ └── myspecialpony.local
├── group_vars
│ └── webservers
├── inventories
│ ├── mycluster
├── roles
│ └── apache
│ ├── tasks
│ │ └── main.yml
│ └── vars
│ └── main.yml
├── vendor
│ └── geerlingguy.php
└── vars
└── main.yml
Templates
Jinja2 python template engine (Symfony/Twig originates from jinja)
├── site.yml
├── roles
│ └── apache
│ ├── tasks
│ ├── ...
│ └── templates
│ └── httpd.j2
- name: write the apache config file
template: src=/srv/httpd.j2 dest=/etc/httpd.conf
...
# prevent Apache from glomming onto all bound IP addresses.
Listen {{ http_port }}
...
Templates
A more advanced example
################################################
# {{ ansible_managed }}
################################################
[Global]
hostname = {{ netatalk.hostname }}
login message = "{{ netatalk.welcome_message }}"
mimic model = {{ netatalk.icon }}
zeroconf = yes
uam list = uams_guest.so
guest account = {{ netatalk.user }}
log level = default:debug
{% for item in netatalk.shares %}
[{{ item.name }}]
path = {{ netatalk.rootpath }}{{ item.name }}
valid users = {{ netatalk.user }}
{% endfor %}
Provisioning
A complete LAMP stack on CentOS 7.1
- hosts: all
sudo: true
vars_files:
- vars/main.yml
pre_tasks:
- selinux: state=disabled # only for demo
- service: name=firewalld state=stopped # only for demo
post_tasks:
- file: path=/etc/httpd/conf.d/welcome.conf state=absent
- service: name=httpd enabled=true state=reloaded
roles:
- { role: repo_epel, tags: repo_epel }
- { role: repo_remi, tags: repo_remi }
- { role: geerlingguy.ntp, tags: ntp }
- { role: common, tags: common }
- { role: mwanjura.mysql, tags: mysql }
- { role: geerlingguy.php, tags: php }
- { role: geerlingguy.apache, tags: apache }
- { role: geerlingguy.composer, tags: composer }
Let's play
$ ansible-playbook site.yml -i inventories/mycluster
Application Deployment
Shared role "ansistrano"
Capistrano like application deployment
Works as a drop-in replacement for Capistrano or Deployer
https://github.com/ansistrano/deploy./deploy.yml
hosts: all
vars:
ansistrano_deploy_from: "./"
ansistrano_deploy_to: "/var/www/html"
ansistrano_after_symlink_tasks_file: "{{ playbook_dir }}/deploy/tasks/after-symlink.yml"
ansistrano_before_symlink_tasks_file: "{{ playbook_dir }}/deploy/tasks/before-symlink.yml"
roles:
- { role: carlosbuenosvinos.ansistrano-deploy }
Structure within your project
├── README.md
├── app
├── composer.json
├── composer.lock
├── src
├── web
├── inventories
│ ├── production
│ ├── development
│ └── staging
├── deploy
│ └── tasks
│ ├── after-symlink.yml
│ └── before-symlink.yml
├── deploy.yml
└── rollback.yml
Structure on your servers
├── current -> ./releases/20150608080601
├── releases
│ ├── 20150605152333
│ └── 20150608080601
│ ├── app
│ ├── src
│ ├── logs -> ../../shared/logs
│ └── ...
├── repo
│ ├── branches
│ ├── config
│ ├── ...
│ └── refs
├── shared
│ ├── vendor
│ ├── assets
│ └── logs
└── revisions.log
"Hooks"
./deploy/tasks/before-symlink.yml
- name: ensure cache dir
file: path={{ ansistrano_release_path.stdout }}/app/cache state=directory mode=0775 recurse=true
- name: ensure log dir
file: path={{ ansistrano_release_path.stdout }}/app/logs state=directory mode=0775 recurse=true
./deploy/tasks/after-symlink.yml
- name: Reload Apache
service: name=httpd state=reloaded
Let's play
$ ansible-playbook deploy.yml -i inventories/production
Tools & Hints
YAML Syntax
It's up to you
Short
- name: ensure cache dir
file: path={{ ansistrano_release_path.stdout }}/app/cache state=directory mode=0775 recurse=true
Correct
- name: ensure cache dir
file:
path: "{{ ansistrano_release_path.stdout }}/app/cache"
state: directory
mode: 0775
recurse: true
Documentation
http://docs.ansible.comAnsible Galaxy
https://galaxy.ansible.com/Ansible Galaxy
Remember?
Install a shared role
$ ansible-galaxy install geerlingguy.php