How to Setup AWS Monitoring with Terraform and NodePing?

7 min read
How to Setup AWS Monitoring with Terraform and NodePing?

If You're using terraform to manage your infrastructure, and you use NodePing to run checks on it, you might consider using terraform-nodeping - a terraform provider for NodePing checks created by our engineers.

Suppose you have some simple project running - for example a simple web server on AWS EC2. Chances are, you use terraform to manage your infrastructure. If that is the case, you probably have a tf that is somewhat similar to this one:

This example is derived from "An Introduction to Terraform" tutorial.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
  }
}

provider "aws" {
  region = "us-east-2"
}

resource "aws_security_group" "instance" {
  name = "terraform-example-instance"  ingress {
    from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "example" {
  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.instance.id]
  user_data = <<-EOF
              #!/bin/bash
              echo "Hello, World" > index.html
              nohup busybox httpd -f -p 8080 &
              EOF
  tags = {
    Name = "terraform-example"
  }
}

Now lets suppose you want to have a mechanism to monitor if your server is alive. It's just that you're really serious about saying "Hello World", and you feel it would be a great loss to the world if your server would go offline for few hours unnoticed, depriving people their greetings. One option you might be interested in (although I don't know why) could be using NodePing. Since you are already using terraform, it will probably be a good idea to manage your NodePing check with that. So...enter terraform-nodeping.

Setting things up

Prerequisites

You will need to compile the provider yourself, so you will be needing a Go version 1.14.2 or newer (older versions might work, but there are no guarantees).

Obviously you will also need terraform.

Installation

At this point terraform-nodeping is not available in Terraform Registry. The only way to get it is to download it from github.

git clone https://github.com/softkraftco/terraform-nodeping.git
cd terraform-nodeping

Next you should set OS_ARCH environment variable to match your system (its the last piece of information displayed if you run go version).

export OS_ARCH=linux_amd64

linux_amd64 is the default assumed architecture. If that's what you're running, you can skip this step.

Now you can install the provider using make.

make install

Provider declaration

Next step is really simple. Just add terraform-nodeping to the list of required providers.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
    nodeping = {
      version = "0.0.1"
      source  = "softkraft.co/terraform/nodeping"
    }
  }
}

Just a side note - it would probably make sense to keep nodeping resources in a separate tf file.

Setting up credentials

To be able to use terraform-nodeping, first you will need a nodeping API key. Unfortunately, API is not available for "Starter" users, so you will need at least the more expensive "Business" plan. Although if you just want to give it a try for now, the free 15-day trial also provides API access.

Once you have the credentials ready, you can pass them to terraform-nodeping in one of two ways. The preferred approach is to define a token in a provider block in your Terraform file.

provider "nodeping" {
  token = "00AAAAAA-A0A0-AAAA-0000-A0AA0A000AAA"
}

As an alternative you can specify a token using an environment variable in your terminal: bash export NODEPING_API_TOKEN=00AAAAAA-A0A0-AAAA-0000-A0AA0A000AAA

Now you're ready to create some NodePing Resources.

Managing Resources

So our goal is to have a simple http check, that will ping our server, and send us an e-mail if it gets some non 200 response. Lets start with a...

Contact resource
resource "nodeping_contact" "example"{
    custrole = "view"
    name = "Example"
    addresses {
        address = "example@expl.com"
        type = "email"
    }
}

Note that if your e-mail is managed by terraform, then instead of hard-coding the e-mail address, you could pass a reference to it.

This example seems to be pretty self-explainatory. To find out more about different available parameters, and their meaning, refer to API documentation.

One really important thing, is that if you would like to have more addresses, for each one of them, you would need to declare a separate address block within a contact. Be aware that in case of terraform-nodeping the order of address blocks makes a difference, and shuffling them will cause terraform to recreate these addresses. This is due to the way nodeping handles assigning addresses to checks. You will see this later.

Schedule (Notification Window)

Schedules allow you to control when your checks are executed. You can find them at your NodePing client panel, under "Account Settings" > "Notification Settings" > "Windows". You'll notice, that there are a few predefined schedules available for you to choose from. You can also create a new one from your client panel, or using terraform-nodeping.

If you wish to go with the last option, add a new nodeping schedule resource declaration to your terraform file. Something like that:

resource "nodeping_schedule" "my_schedule"{
    name = "MySchedule"
    data {
        day = "monday"
        time1 = "16:00"
        time2 = "17:00"
        exclude = false
    }
    data {
        day = "sunday"
        allday = true
    }
}

Notice you need to set a name attribute for your schedule and at least one data block. Also you need to add a separate data block for every day of the week, you want to include. Days omitted will have this configuration:

  • time1 = "6:00"
  • time2 = "18:00"
  • exclude = false
  • allday = false
  • disabled = false

One important thing to remember, is that schedules are referenced by the name parameter. Declaring two schedules with the same name can cause them to overwrite each other in an inpredictable way.

For more details see API documentation on schedules.

Check resource

Last, but not least, the resource this is all about. The check.

resource "nodeping_check" "my_check"{
    label = "MyCheck"
    type = "HTTP"
    target = "http://" + aws_instance.example.public_id
    enabled = "inactive"
    notifications {
        contact = nodeping_contact.my_contact.addresses[0].id
        delay = 1
        schedule = nodeping_schedule.my_schedule.name
    }
    homeloc = "false"
}

Notice how contact (or maybe address) and schedule are referenced in this declaration. If you would want more notifications assigned to this check, you would write a separate notifications block for each one of them.

For a list of available check types and parameters, see API documentation on checks, but be aware that only HTTP, SSL, and SSH are supported at the time of writing.

Putting it all together

So if we combine all the snippets above, we get this code.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.0"
    }
    nodeping = {
      version = "0.0.1"
      source  = "softkraft.co/terraform/nodeping"
    }
  }
}

provider "aws" {
  region = "us-east-2"
}

provider "nodeping" {
  token = "00AAAAAA-A0A0-AAAA-0000-A0AA0A000AAA"
}

resource "aws_security_group" "instance" {
  name = "terraform-example-instance"  ingress {
    from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "example" {
  ami                    = "ami-0c55b159cbfafe1f0"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.instance.id]
  user_data = <<-EOF
              #!/bin/bash
              echo "Hello, World" > index.html
              nohup busybox httpd -f -p 8080 &
              EOF
  tags = {
    Name = "terraform-example"
  }
}

resource "nodeping_contact" "example"{
    custrole = "view"
    name = "Example"
    addresses {
        address = "example@expl.com"
        type = "email"
    }
}

resource "nodeping_schedule" "my_schedule"{
    name = "MySchedule"
    data {
        day = "monday"
        time1 = "16:00"
        time2 = "17:00"
        exclude = false
    }
    data {
        day = "sunday"
        allday = true
    }
}

resource "nodeping_check" "my_check"{
    label = "MyCheck"
    type = "HTTP"
    target = "http://" + aws_instance.example.public_id
    enabled = "inactive"
    notifications {
        contact = nodeping_contact.my_contact.addresses[0].id
        delay = 1
        schedule = nodeping_schedule.my_schedule.name
    }
    homeloc = "false"
}

And there you go! You have your NodePing checks and contacts managed "infrastructure as code" style.