For several years I’ve managed to bend cfEngine 2.0′s architecture to my will. Being an experienced Perl programmer, I was able to abuse the configuration language snytax in order to accomplish a number of strange things including Copy Back and automated management of OSSEC-HIDS. However, there comes a point when the managing the cfengine configs becomes a burdensome and incredibly unmanageable. I mean, sure, I know what they do. How will any of my co-workers understand them?
After several colleagues recommending Puppet, I hesitantly began the slow, brain fscking process of:
- Understanding exactly what I had accomplished with cfEngine.
- Understanding Ruby (ugh, I’m so thankful for Perl)
- Understanding how to express my cfengine feelings in a way Puppet will understand without hurting it’s feelings
- …
- Profit.
cfEngine makes some things incredibly easy to manage. Nearly every command allows you to “define” new classes based on various conditions. This allows to modify a configuration file, and then tell the daemon associated with that config file to restart. However, when I needed to do something highly specialized, I had to create a shell script, copy the shell script to the server and then run the shell script. Passing data back to do something was possible, though it seemed a bit hacky. It separated the customized actions being performed from the dependent actions in the cfEngine configs. If I had to go back later and make changes, I had to look at both the .cf file and the custom shell script in a completely different directory.
With Puppet, these things can be done relatively simply inside the same class file. Also, Puppet can be extended simply through the use of defines (think macros) or complexly through the use of modules. Additionally, Puppet supports templating, classes, inheritance, and explicit order. Where with cfengine I’d have to do something like this:
copy:
s_snmpd.dc_has_snmp::
$(distribute)/snmpd.conf dest=/etc/snmp/snmpd.conf mode=644
server=$(policyhost) type=sum define=dc_restart_snmpd
shellcommands: s_snmpd.dc_restart_snmpd:: "/sbin/service snmpd restart"
class ssh {package {[ "openssh-clients", "openssh-server" ]:ensure => latest}file { "/etc/ssh/sshd_config": mode => 0600, owner => root, group => root, mode => 644, require => Package["openssh-server"], content => template("sshd_config.erb") }service { sshd: subscribe => File["/etc/ssh/sshd_config"], ensure => running, enable => true } }
With this syntax it’s easy to read that the file /etc/ssh/sshd_config is dependent on the openssh-server package and that the sshd service is dependent on that file. Puppet also feels more “cross-platform” as the “service” directive allows me to abstractly describe the service without having to hard code a call to /sbin/service.
Puppet is not without it’s drawbacks. The first of which is that it is Ruby. If you’re not using Ruby on your systems, this means more package installations on those servers. If you’ve been programming in another language, like Perl or Python, it’s another language you have to fight with. The memory usage is much higher than I expected. On some virtual servers, this may be a huge drawback. Consider:

Memory usage for puppetmasterd
Not too bad, but this is shocking:

Memory Usage at ~250 MB prior to restart
Compare this to cfegine:

Memory Usage for cfservd, Yes, Memory Leak.
and:

Memory Usage for cfexecd
Hell, even a long running Perl program using POE and Net::Pcap to decode all packets on our uplink at work (which bursts to ~75mb/sec) isn’t using that much memory:

Memory Usage for PoCo::Pcap based Traffic Inspector
Ultimately, RAM is cheap and my time is expensive. After kludging together configuration management in cfengine for the past three years, I’ve decided to ditch it in favor of a more sane and extensible configuration with Puppet. I’ve got a lot to learn about Puppet still, so as I learn new and more exciting things and Puppet grows, I’ll be sure to share how it’s helping.











30MB for a simple pcap application? Yikes.