From cfEngine to Puppet, A Retrospective

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:

  1. Understanding exactly what I had accomplished with cfEngine.
  2. Understanding Ruby (ugh, I'm so thankful for Perl)
  3. Understanding how to express my cfengine feelings in a way Puppet will understand without hurting it's feelings
  4. ...
  5. Profit.

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"

Utilizing the intermediary "dc_restart_snmpd" class. With Puppet I can explicitly define the relationship with the config file and service:

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:

  • puppetmasterd
    Memory usage for puppetmasterd

Not too bad, but this is shocking:

  • Memory Usage at 300 MB prior to restart
    Memory Usage at ~250 MB prior to restart

Compare this to cfegine:

  • Memory Usage for cfservd, Yes, Memory Leak.
    Memory Usage for cfservd, Yes, Memory Leak.

and:

  • Memory Usage for cfexecd
    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
    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.


Post Navigator

comments powered by Disqus