$30 off During Our Annual Pro Sale. View Details »

Ansible: A Puppet User's Perspective (DevOps Sydney, 2014)

Rob Howard
September 18, 2014

Ansible: A Puppet User's Perspective (DevOps Sydney, 2014)

A talk given at Devops Sydney in September 2014, explaining Ansible to Puppet users, with a divergence into patterns and problems you can expect to encounter with each.

Rob Howard

September 18, 2014
Tweet

More Decks by Rob Howard

Other Decks in Technology

Transcript

  1. ANSIBLE
    A Puppet User's Perspective

    View Slide

  2. What Ansible is.

    View Slide

  3. Ansible is an

    open-source software
    platform for [remotely]
    configuring and
    managing computers.

    View Slide

  4. Basic Bits

    View Slide

  5. Basic Bits
    • "Push" model;

    Control Machine

    talking to

    Dumb Clients

    via SSH.

    View Slide

  6. Basic Bits
    • "Push" model;

    Control Machine

    talking to

    Dumb Clients

    via SSH.
    ➡ "Pull" model;
    Puppet Agents

    talking back to

    Puppet Master

    via SSL.

    View Slide

  7. Basic Bits
    • Tasks

    • Inventories

    • Variables

    • Handlers

    • Playbooks

    • Roles

    View Slide

  8. Basic Bits
    • Tasks

    • Inventories

    • Variables

    • Handlers

    • Playbooks

    • Roles
    ➡ Tasks

    ➡ Nodes + Roles

    ➡ Variables (Hiera)

    ➡ notify  =>  ...  
    ➡ Manifests (ish)

    ➡ Classes + Profiles

    View Slide

  9. Tasks
    -­‐  apt:  
           pkg:  nginx  
           state:  present
    package  {  "nginx":

     ensure  =>  present,

    }

    View Slide

  10. Tasks
    -­‐  service:  
           name:  nginx  
           enabled:  yes  
           state:  started
    service  {  "nginx":

     ensure  =>  started,

     requires  =>

       Package["nginx"],

    }

    View Slide

  11. Tasks
    -­‐  authorized_key:  
       user:  "bert"  
       comment:  "laptop"

       key:  >

         ssh-­‐rsa  AAA...==
    ssh_authorized_key{

     "laptop":

     type  =>  "ssh-­‐rsa",

     key    =>  "AAA...==",

     user  =>

         User["bert"],

     requires  =>

         User["bert"],

    }

    View Slide

  12. Inventories
    [nameservers]

    ns01

    ns02


    [apps]

    app01

    app02


    [staging:children]

    nameservers

    apps

    View Slide

  13. Inventories
    [nameservers]

    ns01

    ns02


    [apps]

    app01

    app02


    [staging:children]

    nameservers

    apps
    • Node definition blocks

    • Node classifiers / Roles

    • Node definitions +
    Environments

    View Slide

  14. Variables
    #  group_vars/all

    prompt_colour:  "black"


    #  group_vars/staging

    prompt_colour:  "green"


    #  group_vars/prod

    prompt_colour:  "red"


    #  host_vars/ns01

    prompt_colour:  >

       special  snowflake

    View Slide

  15. Variables
    #  group_vars/all

    prompt_colour:  "black"


    #  group_vars/staging

    prompt_colour:  "green"


    #  group_vars/prod

    prompt_colour:  "red"


    #  host_vars/ns01

    prompt_colour:  >

       special  snowflake
    #  hiera/data/global

    prompt::colour:  "black"


    #  hiera/data/env/staging

    prompt::colour:  "green"


    #  hiera/data/env/prod

    prompt::colour:  "red"


    #  hiera/data/hosts/ns01  
    prompt::colour:  >

       special  snowflake

    View Slide

  16. Handlers
    -­‐  template:  
         src:  "hosts.j2"  
         dest:  /etc/hosts"  
       notify:  >

           restart  dnsmasq  
    !
    #  ...  
    !
    -­‐  name:  restart  dnsmasq  
       service:  
         name:  dnsmasq

         state:  restarted
    file  {  "/etc/hosts":

     #  ...

     notify  =>

       Service['dnsmasq']

    }

    View Slide

  17. Playbooks
    -­‐  apt:  
           pkg:  nginx  
           state:  present


    -­‐  service:  
           name:  nginx  
           enabled:  yes  
           state:  started
    package  {  "nginx":

     ensure  =>  present,

    }


    service  {  "nginx":

     ensure  =>  started,

     requires  =>

       Package["nginx"],

    }

    View Slide

  18. Playbooks
    -­‐  apt:  
           pkg:  nginx  
           state:  present


    -­‐  service:  
           name:  nginx  
           enabled:  yes  
           state:  started
    package  {  "nginx":

     ensure  =>  present,

    }

    -­‐>

    service  {  "nginx":

     ensure  =>  started,

    }

    View Slide

  19. Playbooks
    -­‐  hosts:  all  
       roles:  
       -­‐  common


    -­‐  include:  blah.yml


    -­‐  hosts:  nameservers

       roles:

       -­‐  nameserver
    node  /^ns\d+/  {

     include  common

     include

       profiles::nameservers

    }

    View Slide

  20. Roles
    roles/

     '-­‐nameserver/

           |-­‐tasks/

           |  |-­‐main.yml

           |  '-­‐dnsmasq.yml

           |-­‐handlers/

           |-­‐templates

           |-­‐files

           '-­‐meta


    -­‐  hosts:  ns01

       roles:

       -­‐  nameserver
    #  manifests/profiles/...

    class  profiles::nameserver  {

     include  foo::dnsmasq


     Blah  {  "etc":  ...  }

     #  ...

    }


    node  ns01  {

       include  profiles::nameserver

    }

    View Slide

  21. Stick it Together
    #  Vars

    my_keys:

    -­‐  "ssh-­‐rsa  AA..."

    -­‐  "ssh-­‐rsa  BB..."


    #  Task

    -­‐  authorized_keys:  
         user:  "app"

         key:  "{{  item  }}"

       with_items:  my_keys

    View Slide

  22. Stick it Together
    #  Vars

    my_keys:

    -­‐  "ssh-­‐rsa  AA..."

    -­‐  "ssh-­‐rsa  BB..."


    #  Task

    -­‐  authorized_keys:  
         user:  "app"

         key:  "{{  item  }}"

       with_items:  my_keys
    #  Hiera  Data

    my_keys:

    -­‐  "ssh-­‐rsa  AA..."

    -­‐  "ssh-­‐rsa  BB..."


    #  Manifest

    create_resources(

       'ssh_authorized_key',

       hiera('my_keys'),

       {  user  =>  'app'  }

    )

    View Slide

  23. Back to Playbooks
    -­‐  apt:  
           pkg:  nginx  
           state:  present


    -­‐  service:  
           name:  nginx  
           enabled:  yes  
           state:  started
    package  {  "nginx":

     ensure  =>  present,

    }


    service  {  "nginx":

     ensure  =>  started,

     requires  =>

       Package["nginx"],

    }

    View Slide

  24. Back to Playbooks
    -­‐  apt:  
           pkg:  nginx  
           state:  present


    -­‐  service:  
           name:  nginx  
           enabled:  yes  
           state:  started
    package  {  "nginx":

     ensure  =>  present,

    }

    -­‐>

    service  {  "nginx":

     ensure  =>  started,

    }

    View Slide

  25. Dependencies:

    Implicit vs Explicit

    View Slide

  26. Dependencies:

    Implicit vs Explicit
    (Ansible) (Puppet)

    View Slide

  27. Ordering:

    Implicit vs Explicit

    View Slide

  28. Ordering:

    Implicit vs Explicit
    (Puppet) (Ansible)

    View Slide

  29. What Ansible isn't.

    View Slide

  30. Ansible is simple
    because it uses YAML.

    View Slide

  31. You may as well say:

    C is simple

    because it uses

    letters, numbers and

    some symbols.

    View Slide

  32. Ansible is

    "Infrastructure as Data",

    not

    "Infrastructure as Code".

    View Slide

  33. It's code!
    It's code even if you're writing
    it in a markup language; it's
    loops, variables, conditions, and
    functions.
    It's about as declarative as my
    mum's Shepherd's Pie recipe.

    View Slide

  34. Ansible Oddities

    View Slide

  35. Dumb Syntax Tricks
    -­‐  copy:  dest=/etc/motd  content="Yeah  nah"  
           

    View Slide

  36. Dumb Syntax Tricks
    -­‐  copy:  >

           dest=/etc/motd

           content="Yeah  nah"  
           

    View Slide

  37. Dumb Syntax Tricks
    -­‐  copy:  >

           dest=/etc/motd

           content={{  motd_message  }}

       vars:

           motd_message:  "Yeah  nah"  

    #  KABOOM

    #  Interpolated  as

    #      dest=/etc/motd  content=Yeah  nah

    View Slide

  38. Dumb Syntax Tricks
    -­‐  copy:  >

           dest=/etc/motd

           content="{{  motd_message  }}"

       vars:

           motd_message:  "Yeah  nah"

    View Slide

  39. Dumb Syntax Tricks
    -­‐  copy:  >

           dest=/etc/motd

           content="{{  motd_message  }}"

       vars:

           motd_message:  'Yeah,  "nah".'  
    !
    #  KABOOM

    #  Interpolated  as

    #      dest=/etc/motd  content="Yeah,  "nah"."

    View Slide

  40. Dumb Syntax Tricks
    -­‐  copy:

           dest:  "/etc/motd"

           content:  "{{  motd_message  }}"

       vars:

           motd_message:  "Yeah,  nah"  
           

    View Slide

  41. Safety: with_items
    -­‐  copy:

           dest:  "/home/{{  item  }}.ssh/
    authorized_keys"

           content:  "{{  lookup('file',  "keys"  +  
    item)  }}"

       with_items:

       -­‐  "bert"

       -­‐  "missing"

    View Slide

  42. Safety: with_items
    -­‐  copy:

           dest:  "/home/{{  item  }}.ssh/
    authorized_keys"

           content:  "{{  lookup('file',  "keys"  +  
    item)  }}"

       with_items:

       -­‐  "bert"

       -­‐  "missing"

    View Slide

  43. Other Things
    • Reusing code is tough and inflexible;
    everything needs to be a role that you
    then stick together in the meta/main.yml

    View Slide

  44. Other Things
    • Reusing code is tough and inflexible;
    everything needs to be a role that you
    then stick together in the meta/main.yml

    • Third-party Roles via Ansible Galaxy is
    great, ... but a lot of it's new or naïve;
    unless you're lucky, you end up modifying
    a lot of it.

    View Slide

  45. Puppet Problems

    View Slide

  46. Looping
    #  Vars

    my_keys:

     -­‐  user:  app

         key:  "..."

       

    #  Task

    -­‐  authorized_keys:  
         user:  "{{  item.user  }}"

         key:  "{{  item.key  }}"

       with_items:  my_keys

    View Slide

  47. Looping
    #  Vars

    my_keys:

     -­‐  user:  app

         key:  "..."

       

    #  Task

    -­‐  authorized_keys:  
         user:  "{{  item.user  }}"

         key:  "{{  item.key  }}"

       with_items:  my_keys
    #  Hiera  Data

    my_keys:

    -­‐  "ssh-­‐rsa  AA..."

    -­‐  "ssh-­‐rsa  BB..."


    #  Manifest

    create_resources(

       'ssh_authorized_key',

       hiera('my_keys'),

       {  user  =>  'app'  }

    )

    View Slide

  48. Looping
    #  Vars

    my_keys:

     -­‐  user:  app

         key:  "..."

       

    #  Task

    -­‐  authorized_keys:  
         user:  "{{  item.user  }}"

         key:  "{{  item.key  }}"

       with_items:  my_keys
    #  Hiera  Data

    my_keys:

    -­‐  "ssh-­‐rsa  AA..."

    -­‐  "ssh-­‐rsa  BB..."


    #  Manifest

    create_resources(

       'ssh_authorized_key',

       hiera('my_keys'),

       {  user  =>  'app'  }

    )
    Structure of Heira data needs to match closely
    to what's consuming it (eg. SshAuthorizedKeys).

    Ansible is much more flexible (see above), and
    can avoid the massive single-resource-declaration
    problem lurking in the bottom-right. :-)

    View Slide

  49. Single Declaration
    Package  {  "ntp":  
       ensure  =>  present,  
    }


    #  ...


    SomeThing  {  "foo":  
       #  ...

       requires  =>  Package['ntp'],

    }

    View Slide

  50. Single Declaration
    Package  {  "ntp":  
       ensure  =>  present,  
    }


    #  ...


    SomeThing  {  "foo":  
       #  ...

       requires  =>  Package['ntp'],

    }
    Single-declaration-only for resources
    makes writing reusable third-party
    Puppet modules really hard.

    These (long) bug threads explain it well:

    • https://projects.puppetlabs.com/
    issues/18490

    • https://tickets.puppetlabs.com/
    browse/PUP-1968

    View Slide

  51. • Ansible not as "simple" as touted.

    • Ansible has unsafe edge-cases to skirt
    around.

    • Ansible makes you remember the
    dependency ordering yourself, rather
    than encoding it.
    Summin' up.

    View Slide

  52. • Puppet has big problems it's going to find
    difficult to solve without breaking everything.
    (You can't silently merge two identical Service
    definitions if you have globals and could-
    change-on-each-call state hanging around.)

    • Puppet makes you write the dependency
    information, but then squanders it.

    • Using generic data is tough without custom
    mangling or the experimental syntax engine.
    Summin' up.

    View Slide

  53. Fin.

    Rob Howard

    @damncabbage

    https://speakerdeck.com/damncabbage/

    View Slide