nrpe server in golang

nrpe server

In the first part we described how to use golang implementation of NRPE client to retrieve information from a running NRPE daemon.

The library enables reusing of the existing nagios nrpe checks and integrating them into a golang monitoring-microservice. It requires NRPE daemon running on the server where the target service is running.

The data collected by nrpe daemon provided by nagios plugins narrows down to server-specific parameters such as CPU/memory usage, DiskIO, network,server load and other system metrics. Our wish, however, was to have service specific checks, indicating the health and status of our microservices also served through NRPE daemon, so that we can have them served both from our golang monitoring application - through the previously descibed client, and through the standart nagios check_nrpe client.

In this post I will go through a basic setup to get the daemon running and have the application specific checks served through nrpe. For the impatient ones, there is a quick-start example on the github page.

Basic HTTP service

As a base service that needs monitoring I will use the simple http app I had as an example in one of my earlier posts. You can check out the service we are going to integrate the NRPE daemon into.

The final, intergrated version is available under the branch nrpe_server.

The description of the flow will assume that you have checked out the master version:

git@github.com:envimate/gomongo.git

cd gomongo

NRPE server code

I created a separate package in the gomongo folder to place our monitoring code there.

mkdir -p src/gomongo/monitoring

And here is the content of the nrpeServer.go with some in-depth explanation


package monitoring

import (
    "fmt"
    "net"

    "log"

    "github.com/envimate/nrpe"
)

// maps the command name to the handler
var mux map[string]commandHandler

// all handlers of the commands are of this type
type commandHandler func(args []string) *nrpe.CommandResult

// concrete example of command handler implementation
// here goes your custom code, that answers with a result to the command
func requestCountHandler(args []string) *nrpe.CommandResult {
    result := &nrpe.CommandResult{
        StatusLine: "0",
        StatusCode: nrpe.StatusUnknown,
    }

    return result
}


// Centralized handler for all the commands, checks the pre-initialized map and forwards the processing of the command to the mapped handler function
func nrpeHandler(c nrpe.Command) (*nrpe.CommandResult, error) {
    // handle nrpe command here
    var handler commandHandler
    var ok bool
    if handler, ok = mux[c.Name]; !ok {
        log.Printf("Got unknown command %s", c.Name)
        return nil, fmt.Errorf("Unknown command received %+v", c)
    }

    return handler(c.Args), nil
}


// centralized connection handler. forwards the connection to the commandHandler.
func connectionHandler(conn net.Conn) {
    defer conn.Close()
    nrpe.ServeOne(conn, nrpeHandler, true, 0)
}

// Struct holding configuration details for nrpe server - so far just the port where it listens on
type NRPEServer struct {
    Port int
}

func init() {
    // initializing the map of handlers - mapping the name of the incomming command to the function executed
    mux = make(map[string]commandHandler)

    mux["requestCount"] = requestCountHandler
}


// Start the NRPE server
func (nrpeServer *NRPEServer) StartServer() {
    ln, err := net.Listen("tcp", fmt.Sprintf(":%d", nrpeServer.Port))

    if err != nil {
        fmt.Println(err)
        return
    }

    for {
        conn, err := ln.Accept()

        if err != nil {
            fmt.Println(err)
            continue
        }

        // calling the central handler asyncroniously, it will decide where to pass the handling of the command
        go connectionHandler(conn)
    }
 }

The code is explained in details.

  • Everything starts with the StartServer method, which listens to the configured port, and gives the handling of the connection to the connectionHandler function.
  • The connection is then served by the library, and the centralized nrpeHandler is called. It could be a nice improvement to also put the timeout and the ssl parameters into the NRPEServer struct, but let’s skip this for the sake of the example.
  • Our goal here, is to have custom application checks exposed as NRPE commands. To hold the mapping between commands and the concrete handlers I initialize the mux map in the init function.
  • The function nrpeHandler is called for every request, it checks if the command is present in the map and forwards the execution to a conrcete handler, in this case - requestCountHandler which is for the sake of example fixed to return a CommandResult of status unknown. Here you will place your custom logic, and will answer with appropriate status codes.

Some things left to do to make it work

Starting the NRPE Server

To actually start the NRPE Server we need to take the port from the command line arguments


 var port int
 var nrpePort int
 var mongourl string

 func init() {
     flag.IntVar(&port, "port", 8080, "Port on which to listen")
     flag.StringVar(&mongourl, "mongourl", "mongodb://localhost:27017", "the mongodb connection url")
     flag.IntVar(&nrpePort, "nrpePort", 5667, "Port to listen for NRPE requests")
     flag.Parse()
 }
 
 

construct and start the NRPEServer struct


func main() {
    ...
    mux["/"] = defaultHandler
    mux["/city"] = mh.cityIdHandler

    nrpeServer := &monitoring.NRPEServer{
        Port: nrpePort,
    }

    go nrpeServer.StartServer()

    log.Fatal(s.ListenAndServe())
}

Dependencies

The library uses libssl-dev package to implement the encoding of NRPE protocol. Hence you need to have it installed on the server the application is running. In our case that would be our Dockerfile:


FROM golang

RUN apt-get update
RUN apt-get install -y libssl-dev

ADD src/gomongo /go/src/gomongo/
ADD build.sh /go
RUN /go/build.sh

ENTRYPOINT ["/bin/sh", "-c", "exec /go/bin/gomongo -mongourl $MONGO_URL"]

EXPOSE 8080
EXPOSE 5667

Additionally we will also expose the port for NRPE server, which is in this example 5667

Also do not forget to add the mapping in the docker-compose.yml file


version: '2'
services:
    gomongo:
        build:
            context: ../
            dockerfile: docker/Dockerfile
        ports:
            - "80:8080"
            - "5667:5667"
        depends_on:
            - mongodb_import
        restart: always
        container_name: gomongo
        environment:
            - MONGO_URL=mongodb://mongodb:27017
    mongodb_import:
        build:
            context: ../
            dockerfile: docker/mongo_Dockerfile
        depends_on:
            - mongodb
    mongodb:
        image: "mongo"
        command: mongod --noprealloc --nojournal --logpath /dev/null

Result

After all this changes, or simply after checking out the branch from github

git clone git@github.com:envimate/gomongo.git --branch nrpe_server

cd gomongo

And bring the service up through docker-compose

docker-compose -f docker/docker-compose.yml up -d

Now if you have an nrpe client running somewhere, make sure the 2 servers have connectivity and try running the nrpe command through the default nagios plugin.

In my case I had an nrpe nagios client running since my first post so I connected the gomongo docker container to the same network and run the command from there.

docker-compose -f docker/docker-compose.yml up -d
Creating network "docker_default" with the default driver
Creating docker_mongodb_1
Creating docker_mongodb_import_1
Creating gomongo
docker network connect nrpe_client gomongo
docker exec -it nrpe /bin/bash -c "/usr/lib/nagios/plugins/check_nrpe -H gomongo -p 5667 -c requestCount"
0

You can also use the client example to connect to the server we just started.

I hope you find this useful in incorporating monitoring checks!

More Reading
Newer// Progress