Felix Frank
CfgMgmtCamp.eu Gent 2017
Felix - trying to automate all the things since 2004
Speaking of...
...we are hiring in Berlin
Major Puppet spoilers ahead
Welcome!
Many cool things to talk about
So let's talk about game development
http://7te.org/retro-game-collage-wallpaper.html
Games: stateful systems with really great UI
Game design lessons for utility software
Larger game productions not unlike enterprise tools
e.g. European Air War
https://en.wikipedia.org/wiki/European_Air_War
It was doomed
New staff faced dozens of bugs
Hence first task:
add the
Cool Cam
featureUltimately an early gimmick feature saved the project
An early gimmick feature:
$ mgmt run --allow-tmp-prefix \
--puppet 'package { "cowsay": ensure => installed }'
$ mgmt run --allow-tmp-prefix \
--puppet 'file { "/etc/ntp.conf":
content => template("/etc/ntp.conf.erb")
}
~>
service { "ntp": ensure => running }'
$ mgmt run --allow-tmp-prefix \
--puppet 'class { "puppetdb": database => "embedded" }'
$ mgmt run --allow-tmp-prefix \
--puppet /var/local/manifests/hardening.pp
$ mgmt run --allow-tmp-prefix --puppet agent
So how does this work?
let's take a quick deep dive
Two principal parts of Puppet
Side note:
the Puppet compiler
is not actually a compiler
catalog representation
On the wire, the catalog looks very similar to the manifest
{
"tags": ["settings","fflaptop.local","node"],
"name": "fflaptop.local",
"version": 1486222644,
"code_id": null,
"catalog_uuid": "8bd0ee21-8ac9-4393-9ad3-01f8639f4e2c",
"catalog_format": 1,
"environment": "production",
"resources": [
{
"type": "Stage",
"title": "main",
"tags": ["stage","main"],
"exported": false
},
{
"type": "Class",
"title": "Settings",
"tags": ["class","settings"],
"exported": false
},
...
{
"type": "File",
"title": "/tmp/this-is-a-file",
"tags": ["file","class"],
"file": "/home/ffrank/.puppetlabs/etc/puppet/env/production/manifests/site.pp",
"line": 4,
"exported": false,
"parameters": {
"ensure": "present",
"owner": "ffrank",
"group": "www-data"
}
},
{
"type": "Exec",
"title": "/usr/games/cowsay mooo",
"tags": ["exec","node","fflaptop.local","class"],
"file": "/home/ffrank/.puppetlabs/etc/puppet/env/production/manifests/site.pp",
"line": 14,
"exported": false
}
# class { "demo":
# arg => [
# "complex", "value",
# { sensible => false } ]
# }
{
"type": "Class",
"title": "Demo",
"tags": ["class","demo"],
"file": "",
"line": 1,
"exported": false,
"parameters": {
"arg": [
"complex",
"value",
{ "sensible": false }
]
}
}
# object { "/tmp/something":
# content => "things-to-store"
# }
{
"type": "Object",
"title": "/tmp/storage",
"tags": ["object","class"],
"file": "",
"line": 1,
"exported": false,
"parameters": {
"before": [
"Service[ostor]"
],
"content": "things-to-store"
}
},
From catalog to Resource Abstraction Layer
Finally the RAL catalog emerges
Puppet will hand this to the configurer
We remodel it into an mgmt graph instead
The mgmt interface you saw:
$ mgmt run --allow-tmp-prefix \
--puppet 'package { "cowsay": ensure => installed }'
Internally, this invokes a puppet subcommand:
$ puppet mgmtgraph print \
--code 'package { "cowsay": ensure => installed }'
This works courtesy of the ffrank-mgmtgraph module
Transformation
{
"type": "File",
"title": "/etc/ntpd.conf",
"tags": ["file","class"],
"file": "",
"line": 1,
"exported": false,
"parameters": {
"ensure": "present"
}
}
file:
- name: /etc/ntpd.conf
path: /etc/ntpd.conf
state: exists
content:
{
"type": "File",
"title": "/etc/ntpd.conf",
"tags": ["file","class"],
"file": "",
"line": 1,
"exported": false,
"parameters": {
"ensure": "present"
}
}
file:
- name: /etc/ntpd.conf
path: /etc/ntpd.conf
state: exists
content:
...let's convert that
to code
module PuppetX::CatalogTranslation
Type.new :file do
spawn :name do
@resource.title
end
spawn :path do
@resource[:name]
end
rename :ensure, :state do |value|
case value
when :present, :file, :directory
:exists
when :absent
:absent
else
raise "cannot translate file ensure:#{value}"
end
end
Puppet name vs. title
file { "the-ntp-configuration":
path => "/etc/ntpd.conf",
owner => "root",
}
file { "the-other-ntp-configuration-you-see":
path => "/etc/ntpd.conf",
mode => "0644",
}
This is the same file, twice!
Puppet knows this (it's not dumb).
This is how mgmt will see the former
file:
- name: the-ntp-configuration
path: /etc/ntpd.conf
content:
The name is chosen independently of the path.
In Puppet, name and title are distinct but related:
Hence the rule:
spawn :path do
@resource[:name]
end
It always picks up the actual path thanks to the namevar semantics.
This is much safer than looking at the path parameter.
I lied about that rule, by the way.
Here's what it actually looks like:
spawn :path do
if @resource[:ensure] == :directory
@resource[:name] + "/"
else
@resource[:name]
end
end
mgmt has no state=directory.
It uses a trailing slash on the path.
Here's the rule(s) for another resource type
PuppetX::CatalogTranslation::Type.new :whit do
emit :noop
spawn :name do
@resource[:name]
end
end
What, you never heard of the whit resource?
Let's talk about containment
Class['ntp'] -> Class['kerberos']
edges to containers become edges to their whit boundaries
Too Weird; Didn't Listen?
the translation will just deal with
classes, defines,
and relationships between them
Advantages of this approach
on the flipside:
Perhaps you will find a better compromise?
All that being said, there are some
general restrictions when running from
Puppet code.
Puppet will always create a catalog using point-in-time input
The catalog builder derives
mgmt can actually update the graph of a running agent
However, adding support for triggered Puppet graph rebuilds does not seem sensible
So what can you expect?
A sensible goal is to make it possible to run (the) most (popular) Puppet modules through mgmt.
For mgmt to become effective, however, a custom DSL is much more important.
That's because Puppet's DSL has some built-in assumptions.
Example: facts are static values
class { "apache": mpm_module => "prefork" }
$process_cap = Integer( $memorysize_mb / 125.0 - 10.0 )
class { "apache::mod::prefork":
maxclients => $process_cap
}
In a VM, the memory size could change on the fly.
mgmt could receive an event about that and initiate action.
But Puppet cannot emit a structure that reflects this.
In summary
Support for Puppet manifests is neat, it eases testing right now and will help wider adoption.
On the other hand, a custom DSL for mgmt will allow actual new config management practices that a translated catalog cannot implement.
"The Cool Cam" originally published at http://thedailywtf.com/articles/The-Cool-Cam
Bonus content
But what about Puppet resources that mgmt does not have?
Consider the puppet resource command
$ puppet resource nagios_host mail01 ensure=absent
It allows simple resource management from the shell.
There's just one problem:
nagios_host { 'mail01':
host_groups => [ "mailservers", "legacy" ]
}
Non-trivial values like hashes and arrays are not supported.
Solution
Yet another Puppet module that introduces
the puppet yamlresource face:
$ puppet yamlresource nagios_host \
mail01 '{ "ensure": "absent" }'
$ puppet yamlresource nagios_host \
mail01 '{ "host_groups": [ "mailservers", "legacy" ] }'
Now the translator can emit an exec resource
that makes Puppet do the legwork
PuppetX::CatalogTranslation::Type.new :default_translation do
emit :exec
catch_all
spawn :name do
@resource.type.to_s.capitalize + ":" + @resource[:name]
end
def command(resource)
r_type = @resource.type.to_s
r_title = @resource[:name]
r_params = @resource.to_hash.reject { |attr,value|
attr == :name
}
"puppet yamlresource #{r_type} '#{r_title}' " +
"'#{Psych.to_json(r_params).chomp}'"
end
spawn :cmd do
command(@resource)
end
# puppet mgmtgraph print --code \
# 'nagios_host { "mail01": ensure => absent }'
exec:
- name: Nagios_host:mail01
cmd: |-
puppet yamlresource nagios_host 'mail01' '{
"host_name": "mail01", "provider": "naginator",
"ensure": "absent", "target": "/etc/nagios/nagios_host.cfg",
"loglevel": "notice"}'
timeout: 30
shell: /bin/bash
watchshell: /bin/bash
ifshell: /bin/bash
watchcmd: 'while : ;
do echo "puppet run interval passed" ; /bin/sleep 1800 ;
done'
ifcmd: |-
puppet yamlresource nagios_host 'mail01' '{
"host_name": "mail01", "provider": "naginator",
"ensure": "absent", "target": "/etc/nagios/nagios_host.cfg",
"loglevel": "notice"}' --noop --color=false \
| grep -q ^Notice:
state: present
pollint: 0
Starting lots of puppet processes though...well...
http://imgur.com/gallery/gsM3Lt5