for DevOps

Machine provisioning and application deployment with ease.


ansible presentation 06/15 @ Web-Dev-BBQ #4 Stuttgart/Germany
Your flight captains for the next couple of minutes:

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'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?


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

No additional dependencies on the environment.

Secure

ansible does not deploy vulnerable agents to nodes.
Only OpenSSH is required.


Highly reliable

Every run develivers the same result.

Low learning curve

Playbooks use an easy and descriptive language based on YAML.

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.com

Ansible Galaxy

https://galaxy.ansible.com/

Ansible Galaxy

Remember?

Install a shared role

$ ansible-galaxy install geerlingguy.php

Ansible Tower

http://www.ansible.com/tower

Book "ansible for DevOps" by Jeff Geerling

https://leanpub.com/ansible-for-devops

Questions?

Thank you!

Fork me!

And have some instant fun
with ansible

https://github.com/mwanjura/ansible-presentation