Infrastructure
as
Code

With Puppet

Why?

Automation

Snowflake server

definition

Augeas (C library + cmdline, XPath like)

Puppet

Chef

Salt

cfengine

fabric (python library)

pallet (clojure library)

Why Puppet?

Open source

Apache License

Windows Support

Network devices

Cisco IOS over ssh/telnet

Declared state

vs

Actual state

Manifest

Idempotence

Incremental

Architecture

  • client/server: master + agent on nodes + CA
  • stand-alone: masterless, agentless

Stand-alone

Requirements

  • puppet binary
  • manifests
  • ssh server

$ ./scripts/puppet-apply.sh 192.168.0.15
Notice: Compiled catalog for pinocchio in environment production in 0.70 seconds
Notice: /Stage[main]/Httpd/Service[httpd]/ensure: ensure changed 'stopped' to 'running'
Notice: /Stage[main]/Ntpd/Service[ntpd]/ensure: ensure changed 'stopped' to 'running'
Notice: Finished catalog run in 0.75 seconds
OK.

Declarative Language


$ mkdir -p /root/.ssh
$ chmod 700 /root/.ssh
$ KEY='ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAA... laptop_key'
$ echo $KEY > /root/.ssh/authorized_keys
$ chmod 600 /root/.ssh/authorized_keys

vs


ssh_authorized_key { 'laptop_key':
  ensure => present,
  user   => 'root',
  type   => 'ssh-rsa',
  key    => 'AAAAB3NzaC1yc2EAAAADAQABAAA...',
}

Resource


# general syntax form of a resource 
type { unique_id:
    property1 => value1,
    property2 => value2,
    ...
    propertyN => valueN,
}

File


file { "/var/www/index.html":
    ensure  => present,
    owner   => 'apache',
    group   => 'apache',
    mode    => 0400,
    content => "Hello world!",
}

file { "/var/www/":
    ensure  => directory,
    owner   => 'apache',
    group   => 'apache',
    mode    => 0700,
}

Package


package { 'httpd':
    ensure => installed,
}

package { 'nginx':
    ensure => absent,
}

package { 'java':
    ensure => '1.6',
}

package { 'libssl':
    ensure => latest,
}

Service


service { 'httpd':
    ensure => running,
    enable => true,
}

service { 'iptables':
    ensure => stopped,
    enable => false,
}

More types

reference

User

/etc/passwd entry


user { 'apache':
    ensure => present,
}

Group

/etc/group entry


group { 'apache':
    ensure => present,
}

Host

/etc/hosts entry


host { 'ntpserver.example.com':
    ip           => '10.100.10.50',
    host_aliases => 'timeserver',
}

Cron

/etc/crontab entry


cron { "backup cronjob":
  command => "/opt/myapp/backup",
  user    => root,
  hour    => 2,
  minute  => 0
}

Mount

/etc/fstab entry


mount { "/data":
    ensure  => "mounted",
    device  => "/dev/sdb1",
    fstype  => "ext4",
    options => "defaults",
    atboot  => "true",
 }

Tidy

remove unwanted file by age/size


tidy { "tidy log logfiles":
    path    => "/var/log/myapp",
    age     => "60d",
    backup  => false,
    matches => [ "*.log" ],
}

Exec

execute external commands


exec { "untar dem-rules":
    command => "tar xvpf /tmp/dem-rules-2.1.1.tar.gz -C /opt/dem/rules",
    path    => '/bin',
}

Composition

Class


class httpd {

    package { 'httpd':
        ensure => installed,
    }

    service { 'httpd':
        ensure => running,
        enable => true,
    }
}

include httpd

Define


define httpd::virtual_host($server_name, $owner='root') {
    file { "/etc/httpd/conf.d/$server_name.conf":
        ensure  => file,
        owner   => $owner,
        group   => root,
        mode    => 400,
        content => "# This file is managed by puppet

  DocumentRoot /var/www/$server_name
  ServerName $server_name
",
    }
}

httpd::virtual_host { "my first virtualhost":
    server_name => "www.example.com",
}

Node

the main()


node 'pinocchio' {
    include httpd
    include httpd::enable_named_virtual_hosts

    httpd::virtual_host { 'first virtual host':
        server_name => 'cat',
    }

    httpd::virtual_host { 'second virtual host':
        server_name => 'fox',
    }
}

Node regexp


node /^webserver-\d$/ {
  include httpd
}

* using ^$ is a best practice

Inject Configuration

Option #1

directly in manifest


define myservice::auth($username, $password) {
    file { "/etc/myservice/auth.conf":
        ensure  => present,
        content => "# This file is managed by puppet
username=$username
password=$password
",
    }
}

myservice::auth { "configure myservice auth":
    username => "myuser",
    password => "somerandomwords",
}

Option #2

Facter


$ facter | head -n 5
architecture => x86_64
augeasversion => 1.0.0
bios_release_date => 12/01/2006
bios_vendor => innotek GmbH
bios_version => VirtualBox

example:


package { "httpd-$::architecture":
    ensure => installed,
}

Custom facts

can be defined as environment variables


$ export FACTER_MYSERVICE_USERNAME=myuser
$ facter | grep myvar
myservice_username => myuser

or just put a file with key=value pairs in /etc/facter/fact.d


$ cat /etc/facter/facts.d/myservice.txt
myserver_password=somerandomwords
$ facter | grep password
myserver_password=somerandomwords

Use custom facts


myservice::auth { "configure myservice auth":
    username => "$::myservice_username",
    password => "$::myservice_password",
}
More about custom facts

ERB templates

  • if, loops, arbitrary ruby code
  • can be inlined
  • can be provided externally (local file, module, url)

Reusable modules

puppet forge

Dependencies


package { 'httpd':
    ensure  => latest,
}

service { 'httpd':
    ensure  => running,
    enable  => true,
}

Require


package { 'httpd':
    ensure  => latest,
}

service { 'httpd':
    ensure  => running,
    enable  => true,
    require => Package['httpd'],
}

Reactive manifests

Events

refresh

subscribe


package { 'httpd':
    ensure  => latest,
}

service { 'httpd':
    ensure    => running,
    enable    => true,
    subscribe => Package['httpd'],
}

notify


package { 'httpd':
    ensure => latest,
    notify => Service['httpd'],
}

service { 'httpd':
    ensure    => running,
    enable    => true,
}

implicit dependency


package { 'httpd':
    ensure => latest,
    notify => Service['httpd'],
}

service { 'httpd':
    ensure    => running,
    enable    => true,
    # done for you:
    # require => Package['httpd']
}

multiple events

one refresh


package { 'httpd':
    ensure => latest,
}

file { "/etc/httpd/conf.d/https.conf":
    ensure  => present,
    alias   => "https.conf",
    content => "# the configuration",
}

service { 'httpd':
    ensure    => running,
    enable    => true,
    subscribe => [ Package['httpd'], File["https.conf"] ],
}

Extra

  • metaparameters: alias, tag
  • functions: versioncmp, filter, map
  • imperative language: if, switch, scoped variables

One click deployment

End

* created with reveal.js