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, mywatch_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 calledreport
, 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)" ; }