Create a gist now

Instantly share code, notes, and snippets.

@sirex /.gitignore
Last active Dec 1, 2016

What would you like to do?
VilniusPy 2015-06
/env
/default.style
/fonts
/*.pdf
/*.rst.build_temp
venv = env
all: slides.pdf default.style
help:
@echo "make build slides.pdf"
@echo "make clean clean slides.pdf"
clean: ; rm slides.pdf
.PHONY: all clean
slides.pdf: $(venv)/bin/rst2pdf fonts/ubuntu slides.rst slides.style
$< slides.rst \
-b1 \
-l lt \
-s slides.style \
--font-path=parts/fonts/ubuntu-font-family-0.80 \
-e preprocess \
-o $@
default.style: $(venv)/bin/rst2pdf ; $(venv)/bin/rst2pdf --print-stylesheet > $@
fonts/ubuntu: fonts
wget http://font.ubuntu.com/download/ubuntu-font-family-0.80.zip
unzip ubuntu-font-family-0.80.zip
mv ubuntu-font-family-0.80 fonts/ubuntu
rm ubuntu-font-family-0.80.zip
fonts: ; mkdir fonts
$(venv)/bin/rst2pdf: $(venv)/bin/pip requirements.txt
$(venv)/bin/pip install -r requirements.txt
$(venv)/bin/pip: ; virtualenv $(venv)
Pillow==2.8.1
Pygments==2.0.2
argparse==1.2.1
docutils==0.12
pdfrw==0.1
reportlab==3.1.44
rst2pdf==0.93
wsgiref==0.1.2
.. space:: 25
Ansible
.. space:: 25
Mantas Zimnickas
VilniusPy #3
2015-06-30

What is Ansible?

  • Multi-node software deployment and configuration management tool.

  • In other words..

    You can deploy your app multiple times on different servers with one command.

Ansible compared to others

  • Fabric

    You can learn it in 5 minutes, better than shell scripting.

  • Anslibe

    You can learn it in one day, better than Fabric scripting.

  • SaltStack, Chef, Puppet and friends

    I never had enough time to learn any of them, they say, that these tools are better than Ansible.

How I use Ansible

  • One server.
  • Many projects.
  • Very few users using those projects.
  • Apache or Nginx with mod_wsgi or uWSGI.
  • PostgreSQL database (MySQL for older projects).
  • Mostly Python 3.

My expirience with Ansible

akl.lt: Python 3, Nginx, uWSGI, PostgreSQL

https://github.com/python-dirbtuves/akl.lt/tree/master/deployment

atviriduomenys.lt: Python 3, Apache, mod_wsgi, PostgreSQL

https://github.com/akllt/infrastructure/tree/master/websites/atviriduomenys.lt

manoseimas.lt: Python 2, Apache, mod_wsgi, MySQL, CouchDB

https://github.com/ManoSeimas/manoseimas.lt/tree/master/deployment

pydev.lt: Python 3, Apache, mod_wsgi, PostgreSQL

https://github.com/akllt/infrastructure/tree/master/websites/pydev.lt

How to install Ansible?

  • If you run a Debian based distro:

    $ apt install ansible
    
  • pip install also works:

    $ pip install ansible
    
.. page::

Simplest possible way to make Ansible do something:

$ ansible host -c local -i host, -m ping
host | success >> {
    "changed": false,
    "ping": "pong"
}
  • ansible command
  • on host
  • defined in -i host, inventory line
  • using -c local connection backend
  • runs -m ping module

Ansible Playbooks

playbook.yml:

---
- hosts: host
  tasks:
  - ping:
$ ansible-playbook -c local -i host, playbook.yml

It does same thing as:

$ ansible host -c local -i host, -m ping
.. page::

Let's add some defaults using ansible.cfg:

[defaults]
inventory = inventory.cfg

inventory.cfg:

host ansible_connection=local

Now, I don't have to specify inventory file and connection:

$ ansible host -m ping
$ ansible-playbook playbook.yml
.. page::

Modules have arguments:

$ ansible host -m command -a uptime
host | success | rc=0 >>
 up 1 day, 1:17

Default module is command:

$ ansible host -a uptime
host | success | rc=0 >>
 up 1 day, 1:18

Argument can be a YAML expression or key=value string.

.. page::

Same thing using playbook playbook.yml:

---
- hosts: host
  gather_facts: no
  tasks:
  - command: uptime
$ ansible-playbook playbook.yml
PLAY [host] ***********************************************

TASK: [command uptime] ************************************
changed: [host]

PLAY RECAP ************************************************
host : ok=1  changed=1  unreachable=0  failed=0

Playbook structure

---
- hosts: host group name

  vars: a dict of variables

  tasks: list of tasks

  handlers: list of handlers

Roles

roles/
   role/
     files/
     templates/
     tasks/
     handlers/
     vars/
     defaults/
     meta/

playbook.yaml:

---
- hosts: host
  roles:
  - role

Writing your own modules

mymodule.py:

from ansible.module_utils.basic import *

def main():
    module = AnsibleModule(
        argument_spec = dict(
            state     = dict(default='present', choices=['present', 'absent']),
            name      = dict(required=True),
            enabled   = dict(required=True, choices=BOOLEANS),
            something = dict(aliases=['whatever'])
        )
    )

    module.exit_json(changed=True, something_else=12345)

if __name__ == '__main__':
    main()

Ansible Galaxy

https://galaxy.ansible.com/

$ ansible-galaxy install rolename
Category Total Roles
system 1421
development 788
web 721
monitoring 289
networking 258
packaging 248
database 189
... ...

apt module

apt: pkg={{ item }} state=latest
with_items:
- build-essential
- postgresql
- python-psycopg2
- python-dev
- python-pip
- python-virtualenv
- apache2
- libapache2-mod-wsgi-py3
- git

user module

user: >
  name=myproject
  system=yes
  group=www-data
  home={{ home }}

For this to work, you need sudo: yes and home variable:

hosts: host
sudo: yes
vars:
  home: /opt/myproject

PostgreSQL modules

- postgresql_db: >
    name=myproject
    encoding='UTF-8'
    lc_collate='C.UTF-8'
    lc_ctype='C.UTF-8'
    template='template0'
  sudo_user: postgres

- postgresql_user: db=myproject name=myproject priv=ALL
  sudo_user: postgres

- postgresql_user: >
    name=myproject
    role_attr_flags=NOSUPERUSER,NOCREATEDB
  sudo_user: postgres

template module

template: >
  src=templates/apache.conf
  dest=/etc/apache2/sites-enabled/myproject.conf
notify: reload apache
handlers:
- name: reload apache
  service: name=apache2 state=reloaded

apache.conf template

<VirtualHost *:80>
    ServerName {{ server_name }}

    DocumentRoot {{ path }}/var/www

    Alias /media/ {{ path }}/var/www/media/
    Alias /static/ {{ path }}/var/www/static/

    <Directory {{ path }}/var/www>
        Require all granted
    </Directory>

    WSGIDaemonProcess myproject user=myproject group=www-data
    WSGIProcessGroup myproject
    WSGIScriptAlias / {{ path }}/bin/django.wsgi

    <Directory {{ path }}/bin/>
        <Files django.wsgi>
            Require all granted
        </Files>
    </Directory>

    ErrorLog /var/log/apache2/myproject/error.log
    CustomLog /var/log/apache2/myproject/access.log combined
</VirtualHost>

nginx.conf template

server {
    listen 80;
    charset utf-8;
    server_name {{ server_name }};

    location /media  {
        alias {{ path }}/var/www/media;
    }

    location /static {
        alias {{ path }}/var/www/static;
    }

    location / {
        include uwsgi_params;
        uwsgi_pass unix:///run/uwsgi/app/myproject/socket;
    }

    access_log /var/log/nginx/myproject/access.log;
    error_log /var/log/nginx/myproject/error.log;
}

Dealing with passwords

- stat: path=/root/.my.cnf
  register: root_my_cnf

- mysql_user: >
    name=root host=localhost state=present
    password={{ lookup('password', 'secrets/mysqlroot') }}
  when: not root_my_cnf.stat.exists

- template: >
    src=templates/root_my.cnf
    dest=/root/.my.cnf owner=root mode=0600
  when: not root_my_cnf.stat.exists
[client]
user = root
password = {{ lookup('password', 'secrets/mysqlroot') }}
default-character-set = utf8

git module

git: >
  repo=https://github.com/me/myproject
  dest={{ path }}
  force=yes
notify: reload source code
sudo_user: myproject
handlers:
- name: reload source code
  command: touch --no-create {{ path }}/bin/django.wsgi

pip module

pip: >
  virtualenv={{ path }}
  requirements={{ path }}/requirements.txt

command module

command: bin/django migrate --noinput chdir={{ path }}
sudo_user: myproject
command: bin/django collectstatic --noinput chdir={{ path }}
sudo_user: myproject

Environments

vars:
  vars: production

vars_files:
  - vars/{{ vars }}.yml

Changing environment from command line:

$ ansible-playbook playbook.yml -e vars=staging

Testing deployment scripts

Vagrantfile:

Vagrant.configure('2') do |config|
  config.vm.define 'box' do |box|
    box.vm.box = 'ubuntu/trusty64'
    box.vm.network :forwarded_port, guest: 80, host: 8080
    box.vm.synced_folder '.', '/vagrant', disabled: true
    config.vm.provision "ansible" do |ansible|
      ansible.playbook = "deploy.yml"
      ansible.extra_vars = {
        vars: "vagrant",
      }
    end
  end
end
vagrant provision

One command to deploy

$ ansible-playbook deploy.yml

Conclusions

Pros

  • Quite easy to learn.
  • Easy to set up.
  • Better than Fabric or shell scripting (thanks to many modules).

Cons

  • Very slow.
  • Hides output and there is no easy way to get output.
.. page::

.. space:: 50
Thank you
for your attention.
pageSetup:
width: 1024px
height: 768px
margin-top: 2mm
margin-bottom: 0mm
margin-left: 0.2cm
margin-right: 0.2cm
margin-gutter: 0cm
spacing-header: 2mm
spacing-footer: 0mm
firstTemplate: coverPage
fontsAlias:
stdBold: Serif-Bold
stdBoldItalic: Serif-BoldItalic
stdFont: Serif
stdItalic: Serif-Italic
stdMono: Monospace
stdMonoBold: Monospace-Bold
stdMonoBoldItalic: Monospace-BoldItalic
stdMonoItalic: Monospace-Italic
stdSerif: Serif-Roman
embeddedFonts: []
[Ubuntu-R.ttf, Ubuntu-B.ttf, Ubuntu-LI.ttf, Ubuntu-BI.ttf]
fontsAlias :
stdBold: Ubuntu-B
stdBoldItalic: Ubuntu-BI
stdFont: Ubuntu-R
stdItalic: Ubuntu-LI
stdSans: Ubuntu-R
stdSansBold: Ubuntu-B
stdSansBoldItalic: Ubuntu-BI
stdSansItalic: Ubuntu-LI
stdSerif: Ubuntu-R
styles:
bodytext:
alignment: TA_LEFT
fontSize: 8
center:
alignment: TA_CENTER
center-small:
alignment: TA_CENTER
fontSize: 5
small:
spaceBefore: 6
fontSize: 5
small-code:
parent: code
fontSize: 4
code:
fontSize: 6
spaceBefore: 6
spaceAfter: 6
borderPadding: 3
title:
fontSize: 130%
spaceBefore: 0
spaceAfter: 0
heading1:
fontSize: 120%
table-heading:
fontSize: 5
table-body:
fontSize: 4
table:
spaceBefore: 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment