Managing system services with CFEngine

An important system service is not running...

I have experienced that when people talk about a system’s configuration, they mostly think of software to be installed and configuration files to be deployed. That’s true, they are part of a system configuration, but there’s more to it — if Configuration Management was only that, you could rightfully call it “provisioning” instead. For example, another part of a system’s configuration is that certain critical services must be running and/or certain other services must not be running. And in fact, any configuration management tool has provisions to manage system services and ensure they are in the desired state (while they may differ a lot on the “when” and “how” and “how often” the state is checked).

CFEngine is no exception. You can take advantage of ready-to-use frameworks like NCF or EFL, or  roll your own checks. What I’m presenting you today is a simple bundle that I wrote called watch_service, that you can use to ensure that certain system services are up or down.

My approach is similar to NCF’s bundle called service_action in that it tries to provide a generic, system-agnostic bundle to manage services but with a few differences:

  • while service_action relies on information in NCF itself to make the bundle simpler to use, my watch_service relies only on CFEngine’s standard_services knowledge as available in the standard library;
  • while service_action returns information to the agent in the form of namespace-scoped classes (e.g.: the service was in the desired state, or the service was not in the desired state and the problem has been fixed successfully), watch_service only reports about the events by means of another bundle called report, whose code will be also provided in the last part of this post.
  • service_action supports many different actions, watch_service only supports “up” (ensure the service is running) or “down” (ensure the service is not running).

Signature

bundle agent watch_service(service_name,process_name,desired_state)

Parameters

  • service_name: service name to act on
  • process_name: string to look up in the process table to verify if the service is running or not
  • desired_state: string, either “up” or “down”

Classes defined

None (all classes defined by the bundle are bundle-scoped and won’t be visible after the bundle has been evaluated).

Sample usage: Ensure that ntpd is running

On a Debian system the service is called ntp and the process is ntpd. You would use a methods promise like:

"ensure_service_ntp_running"
  usebundle => watch_service("ntp","ntpd","up") ;

On CentOS the service itself is also called ntpd, too, so you’d rather use:

"ensure_service_ntp_running"
  usebundle => watch_service("ntpd","ntpd","up") ;

To support both Debian and CentOS at the same time you’ll probably write your own bundle to pass the right parameters to watch_service:

bundle agent ensure_service_ntp_running
{
  vars:
    debian::
      "service_name" string => "ntp" ;

    centos::
      "service_name" string => "ntpd" ;

  methods:
      "ensure_service_ntp_running"
        usebundle => watch_service("$(service_name)","ntpd","up") ;
}

Source code

bundle agent watch_service(service_name,process_name,desired_state)
# This bundle helps to keep a certain service running or down. It is
# generic so it can be used for many services at once by means of
# variables and iteration. Variables themselves could come from an
# external source (e.g., via ENC), thus allowing for maximum
# flexibility.
#
# Parameters:
# - service_name: service name to act on
# - process_name: string to look up in the process table
# - desired_state: string, "up" or "down"
{
  vars:
      "states"
          slist => { "up","down" },
          comment => "Possible desired states" ;

      "cservice"
          string => canonify("$(service_name)") ;

      "message"
          string => "Keep service $(service_name) $(desired_state)",
          comment => "Message to use in reports" ;
      
      
  classes:
      "keep_$(cservice)_$(states)"
          expression => regcmp("(?i)$(states)","$(desired_state)"),
          comment => "Check if desired_state is $(states)" ;

      "invalid_desired_state_for_$(cservice)"
          expression => "!(keep_$(cservice)_up|keep_$(cservice)_down)",
          comment => "The selected state is invalid" ;


  methods:
    any::
      "report_service_started"
          usebundle => report("$(this.bundle)","services","REPAIRED",
                              "$(message) - STARTED"),
          ifvarclass => "keep_$(cservice)_up.service_$(cservice)_not_running" ;
      
       "report_service_stopped"
          usebundle => report("$(this.bundle)","services","REPAIRED",
                              "$(message) - STOPPED"),
          ifvarclass => "keep_$(cservice)_down.service_$(cservice)_running" ;

      "report_invalid_state_requested"
          usebundle => report("$(this.bundle)","services","FAIL",
                              "$(message) - UNKNOWN STATE $(desired_state)"),
          ifvarclass =>  "invalid_desired_state_for_$(cservice)" ;

  processes:
      "$(process_name)"
          process_count => if_running("service_$(cservice)_running",
                                      "service_$(cservice)_not_running"),
          comment => "Check if $(process_name) is running" ;
      

  services:
      "$(service_name)"
          service_policy => "start",
          ifvarclass => "keep_$(cservice)_up.service_$(cservice)_not_running",
          comment => "Start $(service_name) if it's down" ;

      "$(service_name)"
          service_policy => "stop",
          ifvarclass => "keep_$(cservice)_down.service_$(cservice)_running",
          comment => "Stop $(service_name) if it's up" ;
}

Helper bundles: bundle agent report

bundle agent report(bundle_name,promise,condition,message)
{
  classes:
      "always" expression => "any" ;

  reports:
    always::
      "[$(bundle_name):$(promise) $(condition)] $(message)" ;
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s