blob: 7e314f3d78df30a25bc583865642b54fc9f7d718 [file] [log] [blame]
martin f. krafft3c333222013-06-14 19:27:57 +02001=============================================================
2 reclass recursive external node classification
3=============================================================
4reclass is © 20072013 martin f. krafft <madduck@madduck.net>
5and available under the terms of the Artistic Licence 2.0
6=============================================================
7
8reclass is an "external node classifier" (ENC) as can be used with automation
9tools, such as Puppet, Salt, and Ansible.
10
11The purpose of an ENC is to allow a system administrator to maintain an
12inventory of nodes to be managed, completely separately from the configuration
13of the automation tool. Usually, the external node classifier completely
14replaces the tool-specific inventory (such as site.pp for Puppet, or
15/etc/ansible/hosts).
16
martin f. krafft62239892013-06-14 20:03:59 +020017reclass allows you to define your nodes through class inheritance, while
18always able to override details of classes further up the tree. Think of
19classes as feature sets, as commonalities between nodes, or as tags. Add to
20that the ability to nest classes (multiple inheritance is allowed,
21well-defined, and encouraged), and piece together your infrastructure from
22smaller bits, eliminating redundancy and exposing all important parameters to
23a single location, logically organised.
24
martin f. krafft3c333222013-06-14 19:27:57 +020025In general, the ENC fulfills two jobs:
26
27 - it provides information about groups of nodes and group memberships
28 - it gives access to node-specific information, such as variables
29
30While reclass was born into a Puppet environment and has also been used with
31Salt, the version you have in front of you is a rewrite from scratch, which
32was targetted at Ansible. However, care was taken to make the code flexible
33enough to allow it to be used from Salt, Puppet, and maybe even other tools as
34well.
35
36In this document, you will find an overview of the concepts of reclass, the
37way it works, and how it can be tied in with Ansible.
38
39Quick start Ansible
40~~~~~~~~~~~~~~~~~~~~~
41The following steps should get you up and running quickly. Generally, we will
42be working in /etc/ansible. However, if you are using a source-code checkout
43of Ansible, you might also want to work inside the ./hacking directory
44instead.
45
46Or you can also just look into ./examples/ansible of your reclass checkout,
47where the following steps have already been prepared.
48
49/…/reclass refers to the location of your reclass checkout.
50
51 1. Symlink /…/reclass/adapters/ansible to /etc/ansible/hosts (or
52 ./hacking/hosts)
53
54 2. Copy the two directories 'nodes' and 'classes' from the example
55 subdirectory in the reclass checkout to /etc/ansible
56
57 If you prefer to put those directories elsewhere, you can create
58 /etc/ansible/reclass-config.yml with contents such as
59
60 storage_type: yaml_fs
61 nodes_uri: /srv/reclass/nodes
62 classes_uri: /srv/reclass/classes
63
64 Note that yaml_fs is currently the only supported storage_type, and it's
65 the default if you don't set it.
66
67 3. Check out your inventory by invoking
68
69 ./hosts --list
70
71 which should return 5 groups in JSON-format, and each group has exactly
72 one member 'localhost'.
73
74 4. See the node information for 'localhost':
75
76 ./hosts --host localhost
77
78 This should print a set of keys and values, including a greeting,
79 a colour, and a sub-class called 'RECLASS'.
80
81 5. Execute some ansible commands, e.g.
82
83 ansible -i hosts \* --list-hosts
84 ansible -i hosts \* -m ping
85 ansible -i hosts \* -m debug -a 'msg="${greeting}"'
86 ansible -i hosts \* -m setup
87 ansible-playbook -i hosts test.yml
88
89 6. You can also invoke reclass directly, which gives a slightly different
90 view onto the same data, i.e. before it has been adapted for Ansible:
91
92 /…/reclass.py --pretty-print --inventory
93 /…/reclass.py --pretty-print --nodeinfo localhost
94
95reclass concepts
96~~~~~~~~~~~~~~~~
97reclass assumes a node-centric perspective into your inventory. This is
98obvious when you query reclass for node-specific information, but it might not
99be clear when you ask reclass to provide you with a list of groups. In that
100case, reclass loops over all nodes it can find in its database, reads all
101information it can find about the nodes, and finally reorders the result to
102provide a list of groups with the nodes they contain.
103
104Since the term 'groups' is somewhat ambiguous, it helps to start off with
105a short glossary of reclass-specific terminology:
106
107 node: A node, usually a computer in your infrastructure
108 class: A category, tag, feature, or role that applies to a node
109 Classes may be nested, i.e. there can be a class hierarchy
110 application: A specific set of behaviour to apply to members of a class
111 parameter: Node-specific variables, with inheritance throughout the class
112 hierarchy.
113
114A class consists of zero or more parent classes, zero or more applications,
115and any number of parameters.
116
117A node is almost equivalent to a class, except that it usually does not (but
118can) specify applications.
119
120When reclass parses a node (or class) definition and encounters a parent
121class, it recurses to this parent class first before reading any data of the
122node (or class). When reclass returns from the recursive, depth first walk, it
123then merges all information of the current node (or class) into the
124information it obtained during the recursion.
125
126Information in this context is essentially one of a list of applications or
127a list of parameters.
128
129The interaction between the depth-first walk and the delayed merging of data
130means that the node (and any class) may override any of the data defined by
131any of the parent classes (ancestors). This is in line with the assumption
132that more specific definitions ("this specific host") should have a higher
133precedence than more general definitions ("all webservers", which includes all
134webservers in Munich, which includes "this specific host", for example).
135
136Here's a quick example, showing how parameters accumulate and can get
137replaced.
138
139 All unixnodes (i.e. nodes who have the 'unixnodes' class in their ancestry)
140 have /etc/motd centrally-managed (through the 'motd' application), and the
141 unixnodes class definition provides a generic message-of-the-day to be put
142 into this file.
143
144 All debiannodes, which are descendants of unixnodes, should include the
145 Debian codename in this message, so the message-of-the-day is overwritten in
146 the debiannodes class.
147
148 The node 'quantum.example.org' will have a scheduled downtime this weekend,
149 so until Monday, an appropriate message-of-the-day is added to the node
150 definition.
151
152 When the 'motd' application runs, it retrieves the appropriate
153 message-of-the-day and writes it into /etc/motd.
154
155At this point it should be noted that parameters whose values are lists or
156key-value pairs don't get overwritten by children classes or node definitions,
157but the information gets merged (recursively) instead.
158
159Similarly to parameters, applications also accumulate during the recursive
160walk through the class ancestry. It is possible for a node or child class to
161_remove_ an application added by a parent class, by prefixing the application
162with '~'.
163
164Finally, reclass happily lets you use multiple inheritance, and ensures that
165the resolution of parameters is still well-defined. Here's another example
166building upon the one about /etc/motd above:
167
168 'quantum.example.org' (which is back up and therefore its node definition no
169 longer contains a message-of-the-day) is at a site in Munich. Therefore, it
170 is a child of the class 'hosted@munich'. This class is independent of the
171 'unixnode' hierarchy, 'quantum.example.org' derives from both.
172
173 In this example infrastructure, 'hosted@munich' is more specific than
174 'debiannodes' because there are plenty of Debian nodes at other sites (and
175 some non-Debian nodes in Munich). Therefore, 'quantum.example.org' derives
176 from 'hosted@munich' _after_ 'debiannodes'.
177
178 When an electricity outage is expected over the weekend in Munich, the admin
179 can change the message-of-the-day in the 'hosted@munich' class, and it will
180 apply to all hosts in Munich.
181
182 However, not all hosts in Munich have /etc/motd, because some of them are
183 'windowsnodes'. Since the 'windowsnodes' ancestry does not specify the
184 'motd' application, those hosts have access to the message-of-the-day in the
185 node variables, but the message won't get used
186
187 unless, of course, 'windowsnodes' specified a Windows-specific application
188 to bring such notices to the attention of the user.
189
190reclass operations
191~~~~~~~~~~~~~~~~~~
192While reclass has been built to support different storage backends through
193plugins, currently only the 'yaml_fs' storage backend exists. This is a very
194simple, yet powerful, YAML-based backend, using flat files on the filesystem
195(as suggested by the _fs postfix).
196
197yaml_fs works with two directories, one for node definitions, and another for
198class definitions. It is possible to use a single directory for both, but that
199could get messy and is therefore not recommended.
200
201Files in those directories are YAML-files, specifying key-value pairs. The
202following three keys are read by reclass:
203
204 classes: a list of parent classes
205 appliations: a list of applications to append to the applications defined by
206 ancestors. If an application name starts with '~', it would
207 remove this application from the list, if it had already been
208 added but it does not prevent a future addition.
209 E.g. '~firewalled'
210 parameters: key-value pairs to set defaults in class definitions, override
211 existing data, or provide node-specific information in node
212 specifications.
213 By convention, parameters corresponding to an application
214 should be provided as subkey-value pairs, keyed by the name of
215 the application, e.g.
216
217 applications:
218 - ssh.server
219 parameters:
220 ssh.server:
221 permit_root_login: no
222
223reclass starts out reading a node definition file, obtains the list of
224classes, then reads the files corresponding to these classes, recursively
225reading parent classes, and finally merges the applications list (append
226unless
227
martin f. krafft9b2049e2013-06-14 20:05:08 +0200228Version control
229~~~~~~~~~~~~~~~
230I recommend you maintain your reclass inventory database in Git, right from
231the start.
232
martin f. krafft3c333222013-06-14 19:27:57 +0200233Usage
234~~~~~
235For information on how to use reclass directly, invoke reclass.py with --help
236and study the output.
237
238More commonly, however, use of reclass will happen indirectly, and through
239so-called adapters, e.g. /…/reclass/adapters/ansible. The job of an adapter is
240to translate between different invocation paradigms, provide a sane set of
241default options, and massage the data from reclass into the format expected by
242the automation tool in use.
243
244Configuration file
245~~~~~~~~~~~~~~~~~~
246reclass can read some of its configuration from a file. The file is
247a YAML-file and simply defines key-value pairs.
248
249The configuration file can be used to set defaults for all the options that
250are otherwise configurable via the command-line interface, so please use the
251--help output of reclass for reference. The command-line option '--nodes-uri'
252corresponds to the key 'nodes_uri' in the configuration file. For example:
253
254 storage_type: yaml_fs
255 pretty_print: True
256 output: json
257 nodes_uri: ../nodes
258
259reclass first looks in the current directory for the file called
260'reclass-config.yml' and if no such file is found, it looks "next to" the
261reclass script itself. Adapters implement their own lookup logic.
262
263Integration with Ansible
264~~~~~~~~~~~~~~~~~~~~~~~~
265The integration between reclass and Ansible is performed through an adapter,
266and needs not be of our concern too much.
267
268However, Ansible has no concept of "nodes", "applications", "parameters", and
269"classes". Therefore it is necessary to explain how those correspond to
270Ansible. Crudely, the following mapping exists:
271
272 nodes hosts
273 classes groups
274 applications playbooks
275 parameters host_vars
276
277reclass does not provide any group_vars because of its node-centric
278perspective. While class definitions include parameters, those are inherited
279by the node definitions and hence become node_vars.
280
281reclass also does not provide playbooks, nor does it deal with any of the
282related Ansible concepts, i.e. vars_files, vars, tasks, handlers, roles, etc..
283
284 Let it be said at this point that you'll probably want to stop using
285 host_vars, group_vars and vars_files altogether, and if only because you
286 should no longer need them, but also because the variable precedence rules
287 of Ansible are full of surprises, at least to me.
288
289reclass' Ansible adapter massage the reclass output into Ansible-usable data,
290namely:
291
292 - Every class in the ancestry of a node becomes a group to Ansible. This is
293 mainly useful to be able to target nodes during interactive use of
294 Ansible, e.g.
295
296 ansible debiannode@wheezy -m command -a 'apt-get upgrade'
297 upgrade all Debian nodes running wheezy
298
299 ansible ssh.server -m command -a 'invoke-rc.d ssh restart'
300 restart all SSH server processes
301
302 ansible mailserver -m command -a 'tail -n1000 /var/log/mail.err'
303 obtain the last 1,000 lines of all mailserver error log files
304
305 The attentive reader might stumble over the use of singular words, whereas
306 it might make more sense to address all 'mailserver*s*' with this tool.
307 This is convention and up to you. I prefer to think of my node as
308 a (singular) mailserver when I add 'mailserver' to its parent classes.
309
310 - Every entry in the list of a host's applications might well correspond to
311 an Ansible playbook. Therefore, reclass creates a (Ansible-)group for
312 every application, and adds '_hosts' to the name.
313
314 For instance, the ssh.server class adds the ssh.server application to
315 a node's application list. Now the admin might create an Ansible playbook
316 like so:
317
318 - name: SSH server management
319 hosts: ssh.server_hosts SEE HERE
320 tasks:
321 - name: install SSH package
322 action:
323
324
325 There's a bit of redundancy in this, but unfortunately Ansible playbooks
326 hardcode the nodes to which a playbook applies.
327
martin f. krafftb608e6d2013-06-14 22:10:43 +0200328 It's now trivial to apply this playbook across your infrastructure:
329
330 ansible-playbook ssh.server.yml
331
332 My suggested way to use Ansible site-wide is then to create a 'site'
martin f. krafft3c333222013-06-14 19:27:57 +0200333 playbook that includes all the other playbooks (which shall hopefully be
334 based on Ansible roles), and then to invoke Ansible like this:
335
336 ansible-playbook site.yml
337
338 or, if you prefer only to reconfigure a subset of nodes, e.g. all
339 webservers:
340
341 ansible-playbook site.yml --limit webserver
342
343 Again, if the singular word 'webserver' puts you off, change the
344 convention as you wish.
345
martin f. krafftb608e6d2013-06-14 22:10:43 +0200346 And if anyone comes up with a way to directly connect groups in the
347 inventory with roles, thereby making it unnecessary to write playbook
348 files (containing redundant information), please tell me!
349
martin f. krafft3c333222013-06-14 19:27:57 +0200350 - Parameters corresponding to a node become host_vars for that host.
351
352Contributing to reclass
353~~~~~~~~~~~~~~~~~~~~~~~
354Conttributions to reclass are very welcome. Since I prefer to keep a somewhat
355clean history, I will not merge pull requests. Please send your patches using
356git-format-patch and git-send-e-mail to reclass@pobox.madduck.net.
357
358I have added rudimentary unit tests, and it would be nice if you could submit
359your changes with appropriate changes to the tests. To run tests, invoke
360./run_tests.py in the top-level checkout directory.
361
362If you have larger ideas, I'll be looking forward to discuss them with you.
363
364 -- martin f. krafft <madduck@madduck.net> Fri, 14 Jun 2013 19:30:19 +0200