Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Ansible

 Ansible

Lightning talk on Ansible, given at the Amsterdam Python meetup group on Oct 2nd, 2013

Nick Groenen

October 02, 2013
Tweet

More Decks by Nick Groenen

Other Decks in Programming

Transcript

  1. Ansible
    Radically simple* IT orchestration
    * If you believe the ads http://bit.ly/1hiL9Xl

    View full-size slide

  2. 24 years old
    Loves spicy food
    Developer @ TravelBird
    Python user since 2009
    Linux user since around
    2003
    Big fan of Open Source
    Nick Groenen
    Twitter:
    @NickGroenen
    Web:
    https://zoni.nl
    Email:
    [email protected]

    View full-size slide

  3. Ansible
    Configuration management
    Remote execution
    Deployment

    View full-size slide

  4. Python WSGI app on 2 application servers
    (web1 & web2) behind 1 loadbalancer (lb1)
    Explore through example

    View full-size slide

  5. Vagrant
    Your own cloud on 127.0.0.1

    View full-size slide

  6. Vagrantfile
    Vagrant.configure("2") do |config|
    config.vm.define :lb1 do |lb1|
    lb1.vm.box = "raring-server-cloudimg-amd64-vagrant"
    lb1.vm.network :private_network, ip: "192.168.99.10"
    end
    config.vm.define :web1 do |web1|
    web1.vm.box = "raring-server-cloudimg-amd64-vagrant"
    web1.vm.network :private_network, ip: "192.168.99.11"
    end
    config.vm.define :web2 do |web2|
    web2.vm.box = "raring-server-cloudimg-amd64-vagrant"
    web2.vm.network :private_network, ip: "192.168.99.12"
    end
    end

    View full-size slide

  7. Vagrantfile
    Vagrant.configure("2") do |config|
    config.vm.define :lb1 do |lb1|
    lb1.vm.box = "raring-server-cloudimg-amd64-vagrant"
    lb1.vm.network :private_network, ip: "192.168.99.10"
    end
    config.vm.define :web1 do |web1|
    web1.vm.box = "raring-server-cloudimg-amd64-vagrant"
    web1.vm.network :private_network, ip: "192.168.99.11"
    end
    config.vm.define :web2 do |web2|
    web2.vm.box = "raring-server-cloudimg-amd64-vagrant"
    web2.vm.network :private_network, ip: "192.168.99.12"
    end
    end

    View full-size slide

  8. $ vagrant up
    Bringing machine 'lb1' up with 'virtualbox' provider...
    Bringing machine 'web1' up with 'virtualbox' provider…
    Bringing machine 'web2' up with 'virtualbox' provider...

    View full-size slide

  9. Ansible leverages SSH. No daemons to setup,
    no (extra) permissions to manage, we're ready
    to roll!
    pip install ansible
    or
    aptitude install ansible
    or
    yum install ansible
    or
    emerge ansible

    View full-size slide

  10. /etc/ansible/hosts
    [loadbalancers]
    lb1 ansible_ssh_host=192.168.99.10
    [webservers]
    web1 ansible_ssh_host=192.168.99.11
    web2 ansible_ssh_host=192.168.99.12
    ansible_ssh_host is only necessary because Vagrant VM's aren't in DNS

    View full-size slide

  11. Nesting groups
    [loadbalancers]
    lb1 ansible_ssh_host=192.168.99.10
    [webservers]
    web1 ansible_ssh_host=192.168.99.11
    web2 ansible_ssh_host=192.168.99.12
    [exampleapp:children]
    webservers

    View full-size slide

  12. Needs nginx
    Needs to know webservers
    Loadbalancer

    View full-size slide

  13. Playbooks describe series of states to be
    enforced
    ansible-playbook

    View full-size slide

  14. playbook.yml
    - hosts: loadbalancers
    user: vagrant
    sudo: true
    tasks:
    - name: Install nginx
    apt: name=nginx state=present
    - name: Install nginx config
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    owner=root group=root mode=644

    View full-size slide

  15. playbook.yml
    - hosts: loadbalancers
    user: vagrant
    sudo: true
    tasks:
    - name: Install nginx
    apt: name=nginx state=present
    - name: Install nginx config
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    owner=root group=root mode=644

    View full-size slide

  16. playbook.yml
    - hosts: loadbalancers
    user: vagrant
    sudo: true
    tasks:
    - name: Install nginx
    apt: name=nginx state=present
    - name: Install nginx config
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    owner=root group=root mode=644

    View full-size slide

  17. playbook.yml
    - hosts: loadbalancers
    user: vagrant
    sudo: true
    tasks:
    - name: Install nginx
    apt: name=nginx state=present
    - name: Install nginx config
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    owner=root group=root mode=644

    View full-size slide

  18. What if nginx.conf
    changes?

    View full-size slide

  19. playbook.yml
    # Some stuff omitted to save space
    tasks:
    - name: Install nginx config
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    owner=root group=root mode=644
    notify:
    - restart nginx
    handlers:
    - name: restart nginx
    service: name=nginx state=restarted

    View full-size slide

  20. playbook.yml
    # Some stuff omitted to save space
    tasks:
    - name: Install nginx config
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    owner=root group=root mode=644
    notify:
    - restart nginx
    handlers:
    - name: restart nginx
    service: name=nginx state=restarted

    View full-size slide

  21. Mitigating danger of
    syntax errors

    View full-size slide

  22. playbook.yml
    # Some stuff omitted to save space
    tasks:
    - name: Install nginx config
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    owner=root group=root mode=644
    validate="nginx -t -c %s"
    notify:
    - restart nginx
    handlers:
    - name: restart nginx
    service: name=nginx state=restarted

    View full-size slide

  23. playbook.yml
    # Some stuff omitted to save space
    tasks:
    - name: Install nginx config
    template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf
    owner=root group=root mode=644
    validate="nginx -t -c %s"
    notify:
    - restart nginx
    handlers:
    - name: restart nginx
    service: name=nginx state=restarted

    View full-size slide

  24. failed: [lb1] => {"failed": true}
    msg: failed to validate: rc:1 error:nginx:
    [emerg] directive "pid" is not terminated by ";" in /home/vagrant/.
    ansible/tmp/ansible-1380702362.41-73682544006063/source:5
    nginx: configuration file /home/vagrant/.ansible/tmp/ansible-
    1380702362.41-73682544006063/source test failed
    FATAL: all hosts have already failed -- aborting
    If you screwed up

    View full-size slide

  25. nginx.conf.j2
    {{ ansible_managed }}
    user www-data;
    worker_processes {{ ansible_processor_cores }};
    pid /run/nginx.pid;
    events {
    worker_connections {{ nginx_worker_connections }};
    }

    View full-size slide

  26. nginx.conf.j2
    {{ ansible_managed }}
    user www-data;
    worker_processes {{ ansible_processor_cores }};
    pid /run/nginx.pid;
    events {
    worker_connections {{ nginx_worker_connections }};
    }

    View full-size slide

  27. nginx.conf.j2
    {{ ansible_managed }}
    user www-data;
    worker_processes {{ ansible_processor_cores }};
    pid /run/nginx.pid;
    events {
    worker_connections {{ nginx_worker_connections }};
    }

    View full-size slide

  28. nginx.conf.j2
    {{ ansible_managed }}
    user www-data;
    worker_processes {{ ansible_processor_cores }};
    pid /run/nginx.pid;
    events {
    worker_connections {{ nginx_worker_connections }};
    }

    View full-size slide

  29. Interlude: Remote execution
    ansible --module-name setup --user vagrant lb1

    View full-size slide

  30. Which kernels am I running?
    $ ansible --module-name shell --args "uname -srv" --user
    vagrant all

    View full-size slide

  31. lb1 | success | rc=0 >>
    Linux 3.8.0-30-generic #44-Ubuntu SMP Thu Aug 22 20:52:24
    UTC 2013
    web1 | success | rc=0 >>
    Linux 3.8.0-30-generic #44-Ubuntu SMP Thu Aug 22 20:52:24
    UTC 2013
    web2 | FAILED => SSH encountered an unknown error during
    the connection. We recommend you re-run the command
    using -vvvv, which will enable SSH debugging output to help
    diagnose the issue

    View full-size slide

  32. nginx.conf.j2 (continued)
    upstream exampleapp {
    {% for host in groups['webservers'] %}
    server {{ hostvars[host]['ansible_ssh_host'] }}:9001;
    {% endfor %}
    }
    server {
    listen 80;
    server_name example.vagrant;
    location / {
    uwsgi_pass exampleapp;
    }
    }

    View full-size slide

  33. nginx.conf.j2 (continued)
    upstream exampleapp {
    {% for host in groups['webservers'] %}
    server {{ hostvars[host]['ansible_ssh_host'] }}:9001;
    {% endfor %}
    }
    server {
    listen 80;
    server_name example.vagrant;
    location / {
    uwsgi_pass exampleapp;
    }
    }

    View full-size slide

  34. Take it for a spin
    ansible-playbook playbook.yml

    View full-size slide

  35. If all went well
    lb1 : ok=3 changed=2 unreachable=0 failed=0

    View full-size slide

  36. Run it again!
    lb1 : ok=3 changed=0 unreachable=0 failed=0

    View full-size slide

  37. Needs nginx
    Needs to know webservers
    Loadbalancer

    View full-size slide

  38. Needs nginx
    Needs to know webservers
    Loadbalancer

    View full-size slide

  39. Needs nginx
    Needs to know webservers
    Loadbalancer

    View full-size slide

  40. Need uwsgi
    Need application code
    Application servers

    View full-size slide

  41. Need uwsgi no time for this
    Need application code
    Application servers

    View full-size slide

  42. playbook.yml
    - hosts: webservers
    user: vagrant
    sudo: true
    tasks:
    - name: Install dependencies
    apt: name={{ item }} state=present
    with_items:
    - python-virtualenv
    - python-pip
    - git

    View full-size slide

  43. playbook.yml
    - hosts: webservers
    user: vagrant
    sudo: true
    tasks:
    - name: Install dependencies
    apt: name={{ item }} state=present
    with_items:
    - python-virtualenv
    - python-pip
    - git

    View full-size slide

  44. playbook.yml
    - hosts: webservers
    user: vagrant
    sudo: true
    tasks:
    - name: Install dependencies
    apt: name={{ item }} state=present
    with_items:
    - python-virtualenv
    - python-pip
    - git

    View full-size slide

  45. playbook.yml
    - hosts: webservers
    user: vagrant
    sudo: true
    tasks:
    - name: Install dependencies
    apt: name={{ item }} state=present
    with_items:
    - python-virtualenv
    - python-pip
    - git

    View full-size slide

  46. - name: Checkout application code
    git: repo=https://gist.github.com/6164386.git
    dest=/srv/exampleapp
    version=HEAD
    update=yes
    notify:
    - reload uwsgi instance
    - name: Install requirements
    pip: state=present virtualenv=/srv/exampleapp
    requirements=/srv/exampleapp/requirements.txt
    playbook.yml

    View full-size slide

  47. - name: Checkout application code
    git: repo=https://gist.github.com/6164386.git
    dest=/srv/exampleapp
    version=HEAD
    update=yes
    notify:
    - reload uwsgi instance
    - name: Install requirements
    pip: state=present virtualenv=/srv/exampleapp
    requirements=/srv/exampleapp/requirements.txt
    playbook.yml

    View full-size slide

  48. Need uwsgi
    Need application code
    Application servers

    View full-size slide

  49. ansible-playbook playbook.yml
    lb1 : ok=3 changed=0 unreachable=0 failed=0
    web1 : ok=4 changed=3 unreachable=0 failed=0
    web2 : ok=4 changed=3 unreachable=0 failed=0

    View full-size slide

  50. Python WSGI app on 2 application servers
    (web1 & web2) behind 1 loadbalancer (lb1)
    Deployed!

    View full-size slide

  51. Recap
    Configuration management
    Remote execution
    Deployment

    View full-size slide

  52. But wait! There's more!
    (If there's time)

    View full-size slide

  53. Rolling updates
    - hosts: webservers
    max_fail_percentage: 25
    serial: 2
    tasks:
    - do_stuff_here

    View full-size slide

  54. Delegating actions
    - hosts: webservers
    serial: 2
    tasks:
    - name: Remove from loadbalancer
    shell: remove_from_lb_command
    delegate_to: lb1
    - do_stuff_here
    - name: Add to loadbalancer
    shell: add_to_lb_command
    delegate_to: lb1

    View full-size slide

  55. Tags
    tasks:
    - apt: name={{ item }} state=installed
    with_items:
    - nginx
    - memcached
    tags:
    - packages
    - template: src=templates/src.j2 dest=/etc/foo.conf
    tags:
    - configuration

    View full-size slide

  56. Tags
    Run with:
    $ ansible-playbook example.yml --tags "packages"
    $ ansible-playbook example.yml --tags "configuration"
    $ ansible-playbook example.yml \
    --tags "configuration,packages"

    View full-size slide

  57. Prompting for input
    vars_prompt:
    - name: "some_password"
    prompt: "Enter password"
    private: yes
    - name: "release_version"
    prompt: "Product release version"
    private: no

    View full-size slide

  58. Fine-grained targeting
    Sequential:
    www[01:50].example.com
    db-[a:f].example.com
    Exclusion:
    webservers:!amsterdam
    Intersection:
    webservers:&staging
    Combinations possible!

    View full-size slide

  59. Looking up data
    - hosts: localhost
    tasks:
    - name: Print result of DNS lookup
    shell: echo {{ lookup('dnstxt', 'example.com') }}
    - name: Print file contents
    debug: msg="Motd content: {{ lookup('file', '/etc/motd') }}"

    View full-size slide

  60. ● External inventory scripts
    ● Python API
    ● Custom modules
    ● Custom Jinja2 filters
    ● Moar!
    And more...

    View full-size slide

  61. Closing words
    ● Ignored all best practices for demonstration
    (use roles!)
    ● Made many assumptions
    http://www.ansibleworks.com/quickstart-video/
    http://www.ansibleworks.com/docs

    View full-size slide