During the last weeks I received a lot of positive feedback on my blog posts and the Cfengine 3 code snippets so I started to post them on Twitter. Especially my little article about monitoring the CPU load with Cfengine 3 gained some attention there, that is why I am very motivated to dive in […]

Author:

During the last weeks I received a lot of positive feedback on my blog posts and the Cfengine 3 code snippets so I started to post them on Twitter. Especially my little article about monitoring the CPU load with Cfengine 3 gained some attention there, that is why I am very motivated to dive in deeper into the monitoring possibilities provided by Cfengine 3.

One thing I need from time to time is keeping an eye on Apache, a web server I use very often. There are a lot of ways of how a web server can fail (e.g. eating up too much RAM or suddenly giving unexpected HTTP response codes :o), but checking if its process is running and if the right response is given on HTTP requests should be sufficient in most cases.

Writing the Cfengine 3 code for monitoring the Apache web server

body common control {
        version         => "1.0";
        inputs          => { "cfengine_stdlib.cf" };
        bundlesequence  => { "check_process", "check_tcp_response" };
}

bundle agent check_process {
  vars:
        "web_srv_process" string        => "apache2";
        "init_scripts_path" string      => "/etc/init.d";

  processes:
        "$(web_srv_process)"
          comment       => "Checking if our web server $(web_srv_process) is running.",
          restart_class => "restart_$(web_srv_process)";

  commands:
        "${init_scripts_path}/${web_srv_process} start"
          comment       => "Restarting the web server $(web_srv_process).",
          ifvarclass    => "restart_${web_srv_process}",
          classes       => if_repaired("reportWebSrvRestart");

  reports:
        reportWebSrvRestart::
          "The web server $(web_srv_process) was not running and had to be restarted!";
}

bundle agent check_tcp_response {
  vars:
        "read_web_srv_response" string  => readtcp("php.net", "80", "GET /manual/en/index.php HTTP/1.1$(const.r)$(const.n)Host: php.net$(const.r)$(const.n)$(const.r)$(const.n)", 60);

  classes:
        "expectedResponse" expression   => regcmp(".*200 OK.*\n.*", "$(read_web_srv_response)");

  reports:
        !expectedResponse::
          "Something is wrong with php.net - see for yourself: $(read_web_srv_response)";

}

Check the code snippet for errors. If no warning or error message occurs you should be safe running the script (make sure to adapt the script to your needs):

/var/cfengine/bin/cf-promises -f /etc/cfengine3/xenuser_org-013-monitoring_apache_web_server.cf
/var/cfengine/bin/cf-agent -f /etc/cfengine3/xenuser_org-013-monitoring_apache_web_server.cf

If Apache was not running and/or the HTTP response did not contain the 200 status code you should have received warnings by now (e.g. look at the syslog). Also the Apache server should have been started automatically.

Analyzing the Cfengine 3 code snippet
As you can see we are using two methods for monitoring the Apache web server. The first one is checking if the process on the local system is running (assuming that the targeted web server is running on the same system). If there is no such process, it will be started by Cfengine 3.
The second method makes sure that the web server also provides the expected HTTP status code which is a good way of ensuring that the configuration is correct or the process did not die etc.

But now let’s step into the details. Since the first part “body common control” should be already familiar to you, we will jump right to the first interesting part:

bundle agent check_process {
  vars:
        "web_srv_process" string        => "apache2";
        "init_scripts_path" string      => "/etc/init.d";

Here the promise of the type “vars” contains two promisers, namely two variables of the type “string”. The first variable defines the process name of the web server, the second one will be needed later for pointing Cfengine 3 to the init.d script of Apache.

In the next section…

  processes:
        "$(web_srv_process)"
          comment       => "Checking if our web server $(web_srv_process) is running.",
          restart_class => "restart_$(web_srv_process)";

…the promise of the type “processes” ensures that the Apache process is running. Note how the variable “web_srv_process” is used in the second line of the code excerpt above for telling Cfengine 3 the name of the desired process. The restart_class is a special method of defining a class name and a condition which is linked to that class. This means “if this process is not running, define a restart class of the given name”.
This class can be used later for performing pre-defined actions when the condition “process is not running” is met.

In the next part of our Cfengine 3 code snippet…

  commands:
        "${init_scripts_path}/${web_srv_process} start"
          comment       => "Restarting the web server $(web_srv_process).",
          ifvarclass    => "restart_${web_srv_process}",
          classes       => if_repaired("reportWebSrvRestart");

… the commands promise contains the command to be executed. In this case we concatenate two variables to the final command. This makes this Cfengine 3 script more dynamic and easier to adapt to other needs. The attribute “ifvarclass” checks if the condition “desired process is not running” is met. With “classes” we invent a new class which will be set to true when the command was executed. In easy-speech this would be “if this promise was repaired – which means if the command was executed and Apache is running again – a class is defined and set to true”.

This new class will be used later…

  reports:
        reportWebSrvRestart::
          "The web server $(web_srv_process) was not running and had to be restarted!";
}

…in a “reports” promise. If this class “reportsWebSrvRestart” is true the text string “The web server…” will be reported to e.g. the syslog.

So far so good. Now let’s get to the part where we directly ask the web server and therefore perform a monitoring on the protocol level:

bundle agent check_tcp_response {
  vars:
        "read_web_srv_response" string  => readtcp("php.net", "80", "GET /manual/en/index.php HTTP/1.1$(const.r)$(const.n)Host: php.net$(const.r)$(const.n)$(const.r)$(const.n)", 60);
}

This new bundle agent called “check_tcp_response” contains again a promise of the type “vars”. There we define a new string variable which makes use of the readtcp() function. The readtcp() function takes exactly four arguments and returns a string:

  • arg #1: host name or IP address of our target
  • arg #2: port number
  • arg #3: protocol query string
  • arg #4: max. number of bytes to read

If you are unsure if your readtcp() definition works simply issue your parameters to a telnet command. You can also leave the protocol query string empty – then readtcp() only will check if a connection can be established.
Now if you wonder about those two variables “const.n” and “const.r” – those are new line and carriage return characters.

  classes:
        "expectedResponse" expression   => regcmp(".*200 OK.*\n.*", "$(read_web_srv_response)");

In the code snippet above the class “expectedResponse” is defined with an expression. The function regcmp checks if the server response contains a pre-defined set of chars. regcmp() takes exactely two arguments:

  • arg #1: regex pattern to look for
  • arg #2: text string to be searched

If the string contains our supplied pattern the class “expectedServerResponse” is set.

But if the class is not “true”…

  reports:
        !expectedResponse::
          "Something is wrong with php.net - see for yourself: $(read_web_srv_response)";
}

…the reports promise will report the warning “Something is wrong…” to e.g. the syslog.

Summary
With simple, but powerful methods provided by Cfengine 3 we are able to have a closer look at a web server and perform a simple monitoring. Of course there are errors we are not able to detect this way, but still this is at least a good attempt of ensuring everything works fine. In this code example I assume that the target web server is running on the machine where this Cfengine 3 script is executed.

As usual, you can download today’s Cfengine 3 script here.

Comments on this entry (2 comments)

Did you like this post? You can share your opinion with us! Simply click here.

Marlon

Hi. Thanks for the article, I have a question, how can we send an email alert if something’s going wrong

Reply

Add Your Comment

Powered by sweet Captcha


three + = 10