How to create an externally facing server using Terraform on Cloud Native Infrastructure Powered by Openstack – plus a bonus!

If you’ve been following my previous posts and videos, you may have already seen how to use the Cloud Native Infrastructure GUI to create a simple externally facing server or you. may have already read my post on using the Openstack CLI.

Let’s be honest though, any self-respecting DevOps guy doesn’t really want to be creating procedural shell scripts to create infrastructure and we certainly don’t want to be using the OpenStack GUI.

Diving straight into using Terraform, (another one of HashiCorps awesome tools), we can easily setup our basic environment that we’d like to spin up a new server.

Firstly, we’ll need to ensure our environment variables are set. I have these in my ~/.zshrc (or ~/.bashrc) file:

export OS_AUTH_URL=https://cor00005.cni.ukcloud.com:13000/v3
export OS_PROJECT_ID=c5223fac91064ac38460171c14eb47ef
export OS_PROJECT_NAME="UKCloud Bobby Demo"
export OS_USER_DOMAIN_NAME="Default"
export OS_DOMAIN_NAME="Default"

export OS_USERNAME="myusername@domain.com"
export OS_PASSWORD=***********
export OS_REGION_NAME="regionOne"

This then means that in our Terraform provider declaration, all we need is:

provider "openstack" {
}

No need to pass anything in, as it’s all read from the environment variable which is pretty handy!

Setting up the network is fairly trivial:

resource "openstack_networking_network_v2" "example_network1" {
  name           = "example_network_1"
  admin_state_up = "true"
}

resource "openstack_networking_subnet_v2" "example_subnet1" {
  name            = "example_subnet_1"
  network_id      = "${openstack_networking_network_v2.example_network1.id}"
  cidr            = "10.10.0.0/24"
  ip_version      = 4
  dns_nameservers = ["8.8.8.8", "8.8.4.4"]

Here we’re creating a basic subnet of 10.10.0.0/24 and setting the DNS nameservers. Without setting the DNS Nameservers, your server won’t be able to make and DNS lookups, which would be pretty rubbish!

The next steps are to create the router and the router interface to enable the network to connect to the all important externally facing network, the Internet:

resource "openstack_networking_router_v2" "example_router_1" {
  name             = "example_router1"
  external_gateway = "893a5b59-081a-4e3a-ac50-1e54e262c3fa"
}

resource "openstack_networking_router_interface_v2" "example_router_interface_1" {
  router_id = "${openstack_networking_router_v2.example_router_1.id}"
  subnet_id = "${openstack_networking_subnet_v2.example_subnet1.id}"
}

We’re going to want our server to have a static/elastic/floating IP, so lets grab one from the pool for use later:

resource "openstack_networking_floatingip_v2" "example_floatip_1" {
  pool = "internet"
}

The last part, is to set the firewall rules so that we can connect to our instance once we create it:

resource "openstack_compute_secgroup_v2" "example_secgroup_1" {
  name = "example_secgroup_1"
  description = "an example security group"
  rule {
    from_port   = 22
    to_port     = 22
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
   rule {
    from_port   = 80
    to_port     = 80
    ip_protocol = "tcp"
    cidr        = "0.0.0.0/0"
  }
}

Basically, all we’re doing is allowing port 80 and port 22 – that will do to quickly get us started!

We now have all the fundamentals in place to create our instance. To fire up the basic CentOS stock image from within OpenStack, you could use the following:

resource "openstack_compute_instance_v2" "example_instance" {
  name            = "example_instance"

  # centos7
  #image_id        = "0f1785b3-33c3-451e-92ce-13a35d991d60"

  flavor_id       = "c46be6d1-979d-4489-8ffe-e421a3c83fdd"
  key_pair        = "${openstack_compute_keypair_v2.test-keypair.name}"
  security_groups = ["${openstack_compute_secgroup_v2.example_secgroup_1.name}"]

  network {
    name        = "${openstack_networking_network_v2.example_network1.name}"
    floating_ip = "${openstack_networking_floatingip_v2.example_floatip_1.address}"
  }
}

Terraform will use everything we’ve created, from the key_pair & the security groups through to the network and floating IP; so go on, lets try it!

terraform apply

You can then connect to your server like a piece of cake:

ssh -i ~/.ssh/yourkey centos@123.45.67.8

Bonus for Reading this far

However, we can go one better than that – firing up an automated server might be cool, but it’s better if it’s serving our app straight out of the box too, right?

What about if we could give terraform our git repo for our software, and have it deploy it as soon as it starts up? We can do that very easily with 2 files, cloudinit.sh and variables.tf.

Lets take a look at the variables.tf first:

variable "clone_location" {
    default = "/srv"
}

variable "git_repo" {
    default = "https://github.com/bobbydeveaux/dummyphp.git"
}

All we’re doing is passing in our location to the GitHub repository of our App, and telling it where to clone it to. We can then make use of this in the cloudinit.sh file:

#!/bin/bash
# Script that will run at first boot via Openstack
# using user_data via cloud-init.

sudo chown -R centos:centos ${clone_location}
git clone ${git_repo} ${clone_location}
cd ${clone_location}

export COMPOSER_HOME=${clone_location}
composer install

sudo APPLICATION_ENV=${application_env} /usr/bin/supervisord -n -c /etc/supervisord.conf

For this to work, we also have to use our LEMP image that we built earlier. You can use your own of course, providing it’s capable of serving PHP, in this case. At the same time we need to provide the cloudinit instruction to terraform:

data "template_file" "cloudinit" {
    template = "${file("cloudinit.sh")}"
    vars {
        application_env = "dev"
        git_repo = "${var.git_repo}"
        clone_location = "${var.clone_location}"
    }
}

…and add this to the instance creation:

user_data =  "${data.template_file.cloudinit.rendered}"

…leaving our final instance creation looking like this:

resource "openstack_compute_instance_v2" "example_instance" {
  name            = "example_instance"

  #coreos
  #image_id        = "8e892f81-2197-464a-9b6b-1a5045735f5d"

  # centos7
  #image_id        = "0f1785b3-33c3-451e-92ce-13a35d991d60"

  # docker nginx
  #image_id        = "e24c8d96-4520-4554-b30a-14fec3605bc2"

  # centos7 lamp packer build
  image_id = "912e4218-963a-4580-a27d-72e5e195c4f5"

  flavor_id       = "c46be6d1-979d-4489-8ffe-e421a3c83fdd"
  key_pair        = "${openstack_compute_keypair_v2.test-keypair.name}"
  security_groups = ["${openstack_compute_secgroup_v2.example_secgroup_1.name}"]

  user_data =  "${data.template_file.cloudinit.rendered}"

  network {
    name        = "${openstack_networking_network_v2.example_network1.name}"
    floating_ip = "${openstack_networking_floatingip_v2.example_floatip_1.address}"
  }
}

If you now apply the terraform changes, your server will boot up, and deploy your application. Head to your floating IP and you will see the application being served. It really is as easy as that and there’s not much more explanation necessary!

To view the full source code for this example, you can check out the github repo.

As always, if you have any questions regarding this tutorial, or you need some pointing, please just tweet me!

Be Sociable, Share!
Share

Leave a Reply

Your email address will not be published. Required fields are marked *