127 lines
3.8 KiB
Markdown
127 lines
3.8 KiB
Markdown
![]() |
---
|
||
|
date: 2015-09-10
|
||
|
title: How I Classify Puppet Nodes
|
||
|
category: devops
|
||
|
---
|
||
|
|
||
|
The basics of defining what modules get applied to a particular node is
|
||
|
really simple in Puppet. Out of the box you just use the hostname and
|
||
|
the FQDN and everyone is happy. You find this everywhere in
|
||
|
documentation, blog posts, presentations, etc. However is has a problem:
|
||
|
scale.
|
||
|
|
||
|
What if you have an elastic infrastructure with nodes being created and
|
||
|
destroyed automatically? What if you want to use the same manifests in
|
||
|
different environment, but use different hostnames? What if you have
|
||
|
stupidly complex host naming conventions that you cannot get your head
|
||
|
round (current day job problem for me :-( )?
|
||
|
|
||
|
In all these cases and more, using the hostname to classify the node
|
||
|
falls down. I like to add in Role that can then be access in 2 ways.
|
||
|
With Hiera, one could do something like:
|
||
|
|
||
|
```
|
||
|
:hierarchy:
|
||
|
- "nodes/%{::trusted.certname}"
|
||
|
- "roles/%{role}"
|
||
|
- "%{environment}"
|
||
|
- "%{osfamily}-osreleasemajor"
|
||
|
- global
|
||
|
```
|
||
|
|
||
|
And with in `site.pp` we can add in a simple `case` statement:
|
||
|
|
||
|
```
|
||
|
node default {
|
||
|
case $::role {
|
||
|
'loadbalancer': {
|
||
|
class { 'haproxy': }
|
||
|
}
|
||
|
'db': {
|
||
|
class { 'mysql': }
|
||
|
}
|
||
|
default: {
|
||
|
notify('no specific classes assigned')
|
||
|
}
|
||
|
}
|
||
|
class { 'security': }
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Now, we can still classify nodes individually but there is something in
|
||
|
between the wider environment and OS categories that we can define
|
||
|
ourselves. Of course we now need to define the role, which is everywhere
|
||
|
from simple to complex or even not completely clear in my head for now.
|
||
|
|
||
|
I create a custom role fact that my manifests will look at. This is
|
||
|
universal, no matter what mechanism is used to populate that fact that
|
||
|
is the only place I will search in my Puppet code.
|
||
|
|
||
|
When your nodes are under Openstack or EC2, this is simple. They both
|
||
|
have the concept of user-defined metadata as key-value pairs. I simple
|
||
|
add a role pair:
|
||
|
|
||
|
```
|
||
|
nova meta <instance-id> set role=loadbalancer
|
||
|
```
|
||
|
|
||
|
You can also set this when you create the instance.
|
||
|
|
||
|
```
|
||
|
nova boot --meta role=loadbalancer --<other-settings> <hostname>
|
||
|
```
|
||
|
|
||
|
Now we just need the fact to look it up.
|
||
|
|
||
|
```
|
||
|
require 'net/http'
|
||
|
require 'json'
|
||
|
require 'uri'
|
||
|
|
||
|
module RoleModule
|
||
|
def self.add_facts
|
||
|
Facter.add("role") do
|
||
|
productname = Facter.value(:productname)
|
||
|
case productname
|
||
|
when 'OpenStack Nova'
|
||
|
setcode do
|
||
|
url= "http://169.254.169.254/openstack/latest/meta_data.json"
|
||
|
uri = URI.parse(url)
|
||
|
http = Net::HTTP.new(uri.host,uri.port)
|
||
|
response = http.get(uri.path)
|
||
|
JSON.parse(response.body)['meta']['role']
|
||
|
end
|
||
|
when 'ProLiant MicroServer'
|
||
|
setcode do
|
||
|
'lab-compute'
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
RoleModule.add_facts
|
||
|
```
|
||
|
|
||
|
What is happening here? First it checks the productname fact so it can
|
||
|
work out what to do. If that is OpenStack Nova then it knows that is
|
||
|
needs to look in the Openstack Metadata service
|
||
|
(<http://169.254.169.254/openstack/latest/meta_data.json>). Our
|
||
|
key/value pair is returned as part of that JSON data and is pushed in to
|
||
|
the role fact.
|
||
|
|
||
|
Likewise, if the productname is an HP Microserver, it will always be a
|
||
|
lab compute node (in my case).
|
||
|
|
||
|
Physical machines otherwise fall down here. There is no way to
|
||
|
dynamically modify their role, but I have a couple of solutions:
|
||
|
|
||
|
- Part of the kickstart file for provisioning the node could populate
|
||
|
a configuration file (`/etc/role.conf`). If the `virtual` fact
|
||
|
contains `physical` the role fact goes and looks it up from there.
|
||
|
- A seperate node classification service that returns a role based on
|
||
|
the contents of various facts that are passed via the custom fact
|
||
|
code.
|
||
|
|
||
|
The important part with both of these is the classification is totally
|
||
|
seperate from my Puppet code.
|