Configuration in Templates is not Configuration as Code

If you have configuration that uses template configuration files, you are practising neither configuration as code nor configuration as data. Having configuration locked away in template files reduces its visibility, and makes it hard for you to query it. It might be easier to write configuration code to use templates, but it will come back to bite you.

One of the first things I implemented in pallet was a templating mechanism, because configuration management tools use templates, right? I even built a template selection mechanism, just like Chef's.

I have come to realise however, that having configuration in template files is not particularly useful. There are three major problems you are likely to encounter. Firstly template files are not visible, secondly you can not query the data in the template files, and lastly you will need to touch multiple files to add or modify parameters.

Visibility at the point of usage is important, especially in a team environment. If you have to find the template file and look at its content when reading your configuration code, then the chances are you assume it hasn't changed, and skip the contents. Making an analogy to the world of software development, templates are like global variables in one sense. You can change the operation of a program with a global variable modified in some obscure place, and in the same way, you can change your system configuration by changing a template file, tucked away in some folder, and not visible from where you are actually calling your configuration crate/recipe.

The ability to query configuration settings allows not just finding out, for example, which directory a log file is in, but also enables you to put tools on top of your configuration data. Template configuration files suffer on two counts here - they are separate text files that require parsing to be read, and the format of each configuration file is different.

The last point concerns the flexibility of your configuration. If you have used template files, with hard coded parameter values, and you then want to modify your configuration to dynamically set one of those hard coded values, you have to modify all the specialised versions of the existing templates, and specify the value in code. You have to touch multiple files - lots of room for making typos.

My goal for pallet then, is to have all configuration supplied as arguments to crates. For most packages a hash map is sufficient an abstraction for providing the data, but when this gets too cumbersome, we'll use a DSL that mirrors the original configuration file language.

Goodbye hidden configuration!

Discuss this post here.

Published: 2010-10-04

Configure Nagios using Pallet

Basic Nagios support was recently added to pallet, and while very simple to use, this blog post should make it even simpler. The overall philosophy is to configure the nagios service monitoring definitions along with the service itself, rather than have monolithic nagios configuration, divorced from the configuration of the various nodes.

As an example, we can configure a machine to have it's ssh service, CPU load, number of processes and number of users monitored. Obviously, you would normally be monitoring several different types of nodes, but there is no difference as far as pallet is concerned.

We start by requiring various pallet components. These would normally be part of a ns declaration, but are provided here for ease of use at the REPL.

 (require   '[pallet.crate.automated-admin-user
    :as admin-user]   '[pallet.crate.iptables :as 'iptables]   '[pallet.crate.ssh :as ssh]   '[pallet.crate.nagios-config
     :as nagios-config]   '[pallet.crate.nagios :as nagios]   '[pallet.crate.postfix :as postfix]   '[pallet.resource.service :as service]) 

Node to be Monitored by Nagios

Now we define the node to be monitored. We set up a machine that has SSH running, and configure iptables to allow access to SSH, with a throttled connection rate (six connections/minute by default).

 (pallet.core/defnode monitored   []   :bootstrap [(admin-user/automated-admin-user)]   :configure [;; set iptables for restricted access
              (iptables/iptables-accept-icmp)
              (iptables/iptables-accept-established)
              ;; allow connections to ssh
              ;; but throttle connection requests
              (ssh/iptables-throttle)
              (ssh/iptables-accept)]) 

Monitoring of the SSH service is configured by simply adding (ssh/nagios-monitor).

Remote monitoring is implemented using nagios' nrpe plugin, which we add with (nagios-config/nrpe-client). To make nrpe accessible to the nagios server, we open the that the nrpe agent runs on using (nagios-config/nrpe-client-port), which restricts access to the nagios server node. We also add a phase, :restart-nagios, that can be used to restart the nrpe agent.

Pallet comes with some configured nrpe checks, and we add nrpe-check-load, nrpe-check-total-proces and nrpe-check-users. The final configuration looks like this:

 (pallet.core/defnode monitored   []   :bootstrap [(admin-user/automated-admin-user)]   :configure [;; set iptables for restricted access
              (iptables/iptables-accept-icmp)
              (iptables/iptables-accept-established)
              ;; allow connections to ssh
              ;; but throttle connection requests
              (ssh/iptables-throttle)
              (ssh/iptables-accept)
              ;; monitor ssh
              (ssh/nagios-monitor)
              ;; add nrpe agent, and only allow
              ;; connections from nagios server
              (nagios-config/nrpe-client)
              (nagios-config/nrpe-client-port)
              ;; add some remote checks
              (nagios-config/nrpe-check-load)
              (nagios-config/nrpe-check-total-procs)
              (nagios-config/nrpe-check-users)]   :restart-nagios [(service/service
                    "nagios-nrpe-server"
                    :action :restart)]) 

Nagios Server

We now configure the nagios server node. The nagios server is installed with (nagios/nagios "nagiospwd"), specifying the password for the nagios web interface, and add a phase, :restart-nagios, that can be used to restart nagios.

Nagios also requires a MTA for notifications, and here we install postfix. We add a contact, which we make a member of the "admins" contact group, which is notified as part of the default host and service templates.

 (pallet.core/defnode nagios   []   :bootstrap [(admin-user/automated-admin-user)]   :configure [;; restrict access
              (iptables/iptables-accept-icmp)
              (iptables/iptables-accept-established)
              (ssh/iptables-throttle)
              (ssh/iptables-accept)
              ;; configure MTA
              (postfix/postfix
               "pallet.org" :internet-site)
              ;; install nagios
              (nagios/nagios "nagiospwd")
              ;; allow access to nagios web site
              (iptables/iptables-accept-port 80)
              ;; configure notifications
              (nagios/contact
              {:contactname "hugo"
               :servicenotificationperiod "24x7"
               :hostnotificationperiod "24x7"
               :servicenotificationoptions
                  "w,u,c,r"
               :hostnotificationoptions
                  "d,r"
               :servicenotificationcommands
                 "notify-service-by-email"
               :hostnotification_commands
                  "notify-host-by-email"
               :email "my.email@my.domain"
               :contactgroups [:admins]})]   :restart-nagios [(service/service "nagios3"
                     :action :restart)]) 

Trying it out

That's it. To fire up both machines, we use pallet's converge command.

 (pallet.core/converge   {monitored 1 nagios 1} service   :configure :restart-nagios) 

The nagios web interface is then accessible on the nagios node with the nagiosadmin user and specified password. Real world usage would probably have several different monitored configurations, and restricted access to the nagios node.

Still to do...

Support for nagios is not complete (e.g. remote command configuration still needs to be added, and it has only been tested on Ubuntu), but I would appreciate any feedback on the general approach.

Discuss this post here.

Published: 2010-08-18

Provisioning Cloud Nodes with Pallet

I recently needed to move a server from dedicated hosting to a cloud server. The existing server had been configured over time by several people, with little documentation. I want to make sure that this time everything was documented, and what better way than doing that than using an automated configuration tool.

Looking around at the configuration tools, I couldn't find one I really liked, so I started Pallet. I'll explain why I didn't use an existing tool below, but first I wanted to show how to manage nodes in Pallet.

   ;; Pull in the pallet namespaces   (require 'pallet.repl)   (pallet.repl/use-pallet)

;; Define a default node (defnode mynode [])

;; Define the cloud account to use (def service (compute-service "provider" "user" "password" :log4j :ssh))

;; Create 2 nodes (converge {mynode 2} service)

This example would create two nodes (cloud vm instances) with the tag "mynode" in your cloud account, as specified in the service. This would give you the smallest size, ubuntu image on most clouds. Of course, to do anything serious, you would want to specify the image you would like, and you would probably like some configuration of the nodes. So carrying on the above example:

   ;; Pull in the needed crates   (use 'pallet.crate.automated-admin-user)   (use 'pallet.crate.java)

;; Define a new node that will use the Java JDK (defnode javanode [:ubuntu :X86_64 :smallest :os-description-matches "[J]+9.10[32]+"] :bootstrap [(automated-admin-user)] :configure [(java :openjdk :jdk)])

;; Create a new node, and remove the previous ones (converge {javanode 1 mynode 0} service)

This would stop the two nodes that were created before, and create a new one, with the specified ubuntu version. On first boot, it would create a user account with your current username, authorize your id_rsa key on that account, and give it sudo permissions. Every time converge is run, it also ensures that the openjdk JDK is installed.

The configuration to be applied is specified as a call to a crate - automated-admin-user and java in the example above. Crates are just clojure functions that specify some configuration or other action on the nodes (they're equivalent to Chef's recipes, which Pallet can also execute using chef-solo). Pallet can be extended with your own crates, and crates can specify general actions, not just configuration. lift is a companion to converge, and can be used to apply crates to existing nodes (including local VM's). The hypothetical example below would execute my-backup-crate on all the "mynode" nodes.

   (defnode mynode [] :backup [(my-backup-crate)])   (lift mynode service :backup) 

This was just a quick overview of Pallet, to give you an idea of what it is. One big area of Pallet not demonstrated here is its command line tool. But that is a topic for another post.

Why Write another Tool?

Now you've seen some examples, I'll try and explain the features that make Pallet distinct from other configuration tools out there.

No Dependencies

The machines being managed require no special dependencies to be installed. As long as they have bash and ssh running, they can be used with pallet. For me this was important - it means that you can use pretty much any image out there, which is great for ad-hoc testing and development.

No Server

Pallet has no central server to set up and maintain - it simply runs on demand. You can run it from anywhere, even over a remote REPL connection.

Everything in Version Control

In pallet, all your configuration is handled in SCM controlled files - there is no database involved. This means that your configuration can always be kept in step with the development of your crates, and the versions of the external crates that you use.

Jar File Distribution of Crates

Custom crates can be distributed as jar files, and so can be published in maven repositories, and be consumed in a version controlled manner. Hopefully this will promote shared crates.

Provisioning, Configuration and Administration

Pallet aims quite wide. You can use it for starting an stopping nodes, for configuring nodes, deploying projects and also for running administration tasks. To be honest, this wasn't an initial design goal, but has come out of the wash that way.

Interested?

Hopefully this has whetted your appetite, and you'll give pallet a try. You can get support via the Google Group, or #pallet on freenode irc.

Discuss this post here.

Published: 2010-05-12

Archive