Using Terraform for blue-green deployments on Triton

December 01, 2017 - by Alexandra White

In this post, you'll learn how to implement the blue-green deployment model using Terraform as a way to deploy with confidence by reducing risk when updating applications and services. If you haven't already, read how to create custom infrastructure images with Packer and how to get started with Terraform.

Deployment models let us deploy application infrastructure with confidence. There are a number of common models including (but not limited to) canary deployment, rolling deployment, and the blue-green deployment model. In the blue-green model, two versions of an application are maintained. One version is live while the other serves as a standby deployment, upon which testing can be done. This allows developers to easily rollback changes if there are problems in a new release. Remove the risk and organizational paralysis by executing changes quickly.

Blue-green deployments ensure you can predictably release updates without disruption to your customer experience.

One way to make the blue-green deployment model even better is to introduce automation. At Joyent, we have fully automated the deployment of our website. With one click to merge a GitHub pull request, a blog post can be published. The amount of stress relief this has provided me as I've needed to make content updates is enormous, and it has given me the desire to automate all the things.

By using Terraform to implement an automated blue-green deployment, you can quickly update your applications, potentially with zero downtime or at the very least a controlled release.

Not interested in the full walkthrough? Download the Terraform files to get started on your own. You can also watch the webinar to see this process in action. Otherwise, carry on.

Tell me more about blue-green deployments

Blue-green deployments are most often associated with different environments for an application, with the colors as indicators for the environments. One color represents the staging environment, while the other represents production.

Why blue-green and not red-orange or purple-yellow? It's a historical term for the deployment model, but the colors are irrelevant. What matters is there are different deployments existing in parallel, and you can seamlessly migrate from one to another.

For this demo, we're going to make updates to a web application. If you weren't a big fan of all of the cats in the last post, this application will probably make you happier. We'll be deploying a happiness randomizer, designed to give random GIFs and inspirational motivation to whomever visits.

Prerequisites

Picard applauds you

Each version of our application is delivered as an immutable, reproducibly created image (in this case, using Packer) that will boot the VM or container we'll run it in. Before we can jump into how to use Terraform to do blue-green deployments, we first have to create our image. If you've already created the cat_randomizer image, you're in luck. You can follow the same instructions with the Happy Randomizer. The Packer configuration file is already included in the GitHub repo and if unmodified, it will create an image named happy_randomizer version 1.0.0.

Later in the demo, we'll update the application image to version 1.1.0, for which there are instructions in the repo.

NOTE: While I think it's fun to use a custom application where you can see the difference (as well as take use of a prior blog exercise), you could just as easily run through this post with two stock Ubuntu images, ubuntu-14.04 and ubuntu-16.04. Check out all of the stock images by executing triton images.

Once happy_randomizer 1.0.0 is available on Triton, create a brand new directory for your Terraform files. The files refer to the images on Triton and do not require local application files. Here's how to create the directory via the terminal:

mkdir randomizer-deployment

NOTE: That directory can be named whatever you see fit.

Initial Terraform configuration files

In this demo, you'll be working with two Terraform configuration files. variables.tf will store, you guessed it, the variables, and main.tf which is what is responsible for the actual deployment.

Variables

There are a number of values that should be included in the variables file including image names and versions, production setting, CNS service names, networks, and instance counts.

The original Happy Randomizer, version 1.0, is configured to be our starting point and designated as our blue deployment.

Create your variables file:

$ touch variables.tf

Using the text-editor of your choice, add the following variables to variables.tf:

#
# Details about all deployments of this application
#
variable "service_production" {
    type = "string"
    description = "Which deployment is considered 'production'? The other is 'staging'. Value can be one of 'blue' or 'green'."
    default = "blue"
}

variable "service_name" {
    type = "string"
    description = "The name of the service in CNS."
    default = "happiness"
}

variable "service_networks" {
    type = "list"
    description = "The name or ID of one or more networks the service will operate on."
    default = ["Joyent-SDC-Public"]
}

#
# Details about the "blue" deployment
#
variable "blue_image_name" {
    type = "string"
    description = "The name of the image for the 'blue' deployment."
    default = "happy_randomizer"
}

variable "blue_image_version" {
    type = "string"
    description = "The version of the image for the 'blue' deployment."
    default = "1.0.0"
}

variable "blue_count" {
    type = "string"
    description = "The number of 'blue' instances to create."
    default = "3"
}

variable "blue_package_name" {
    type = "string"
    description = "The package to use when making a blue deployment."
    default = "g4-highcpu-128M" 
}

Any default you don't fill out will be asked by the Terraform CLI. For example, if the default value for blue_count is left empty, Terraform will ask you for the number of instances you wish to create upon executing terraform apply.

Deployment file

Here's the real meat of our Terraform deployment. Our main configuration file will set up our provider, declare our data sources, and create our blue and green instances.

Terraform can use your environment variables to fill out the provider. If you haven't set those up, create variables in variables.tf for your account name, SSH fingerprint, and the data center name.

In the same directory as our variables file, create main.tf:

$ touch main.tf

Using the text-editor of your choice, add the following variables to main.tf:

#
# You must have installed Terraform v 0.10.0 or above.
#
terraform {
  required_version = ">= 0.10.0"
}

#
# The provider will take the SDC_URL, SDC_ACCOUNT, and SDC_KEY_ID environment vars as defaults. Uncomment data within the triton provider if no environment vars are set up.
#
provider "triton" {
    # url = "https://${var.dc_name}.api.joyent.com"
    # account = "${var.triton_account}"
    # key_id = "${var.triton_key_id}"
}

#
# Details about all deployments of this application
#
data "triton_network" "service_networks" {
  count = "${length(var.service_networks)}"
  name = "${element(var.service_networks, count.index)}"
}

#
# Details about the "blue" deployment
#
data "triton_image" "blue_image" {
    name = "${var.blue_image_name}"
    version = "${var.blue_image_version}"
    type = "lx-dataset"
    most_recent = true
}

resource "triton_machine" "blue_machine" {
    count = "${var.blue_count}"
    name = "blue_happy_${count.index + 1}"
    package = "${var.blue_package_name}"
    image = "${data.triton_image.blue_image.id}"
    networks = ["${data.triton_network.service_networks.*.id}"]
    cns {
        services = ["${var.service_production == "blue" ? var.service_name : "staging-${var.service_name}" }", "blue-${var.service_name}"]
    }
}

The end result of this configuration file will be creating three blue machines on my default data center, us-sw-1.

To better understand the nitty gritty details, read through the configuration file setup for a simple Terraform application.

Step 1: get into the Triton environment

Our Triton provider uses environment variables, so it's important to first get into the Triton environment:

$ eval "$(triton env)"

If your default environment is not the same data center as referred to in your Terraform files, be sure to declare the correct profile.

Step 2: initializing Terraform

Now that your files have been created, you can get ready to execute your infrastructure. First, you must download the providers. This step is critical.

Execute terraform init to download the Triton provider in the background into the local application directory. You should be using Terraform version 0.10.x to execute the following commands.

Step 3: creating the blue infrastructure

Once you've initiated Terraform, you can start the process of deploying happy_randomizer version 1.0.0. That will be your blue infrastructure.

Planning the blue infrastructure

Run terraform plan to review the deployment. The result should look similar to the following:

$ terraform plan -out=blueplan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.triton_image.blue_image: Refreshing state...
data.triton_network.service_networks: Refreshing state...

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + triton_machine.blue_machine[0]
      id:                   <computed>
      cns.#:                "1"
      cns.0.services.#:     "2"
      cns.0.services.0:     "happiness"
      cns.0.services.1:     "blue-happiness"
      created:              <computed>
      dataset:              <computed>
      disk:                 <computed>
      domain_names.#:       <computed>
      firewall_enabled:     "false"
      image:                "b3045d03-75a4-4e3d-97e7-76b1a8de757a"
      ips.#:                <computed>
      memory:               <computed>
      name:                 "blue_happy_1"
      networks.#:           "1"
      networks.0:           "31428241-4878-47d6-9fba-9a8436b596a4"
      nic.#:                <computed>
      package:              "g4-highcpu-128M"
      primaryip:            <computed>
      root_authorized_keys: <computed>
      type:                 <computed>
      updated:              <computed>
  + triton_machine.blue_machine[1]
      id:                   <computed>
      cns.#:                "1"
      cns.0.services.#:     "2"
      cns.0.services.0:     "happiness"
      cns.0.services.1:     "blue-happiness"
      created:              <computed>
      dataset:              <computed>
      disk:                 <computed>
      domain_names.#:       <computed>
      firewall_enabled:     "false"
      image:                "b3045d03-75a4-4e3d-97e7-76b1a8de757a"
      ips.#:                <computed>
      memory:               <computed>
      name:                 "blue_happy_2"
      networks.#:           "1"
      networks.0:           "31428241-4878-47d6-9fba-9a8436b596a4"
      nic.#:                <computed>
      package:              "g4-highcpu-128M"
      primaryip:            <computed>
      root_authorized_keys: <computed>
      type:                 <computed>
      updated:              <computed>
  + triton_machine.blue_machine[2]
      id:                   <computed>
      cns.#:                "1"
      cns.0.services.#:     "2"
      cns.0.services.0:     "happiness"
      cns.0.services.1:     "blue-happiness"
      created:              <computed>
      dataset:              <computed>
      disk:                 <computed>
      domain_names.#:       <computed>
      firewall_enabled:     "false"
      image:                "b3045d03-75a4-4e3d-97e7-76b1a8de757a"
      ips.#:                <computed>
      memory:               <computed>
      name:                 "blue_happy_3"
      networks.#:           "1"
      networks.0:           "31428241-4878-47d6-9fba-9a8436b596a4"
      nic.#:                <computed>
      package:              "g4-highcpu-128M"
      primaryip:            <computed>
      root_authorized_keys: <computed>
      type:                 <computed>
      updated:              <computed>

Plan: 3 to add, 0 to change, 0 to destroy.

This plan was saved to: blueplan

To perform exactly these actions, run the following command to apply:
    terraform apply "blueplan"

This is a preview of exactly what should happen when you execute terraform apply "blueplan".

NOTE: it is recommended and considered best practice to run terraform apply with an output plan, i.e. terraform apply -out=<plan-name>. However, it is possible to leave it off, i.e. terraform apply. One benefit of using a specific plan name is that you can save multiple plans to go back and forth if needed. Additionally, you can see if the environment drifted between the plan and apply phases.

Applying the blue infrastructure

Run terraform apply "blueplan" to set your infrastructure plan into motion, deploying our containers to us-sw-1.

$ terraform apply "blueplan"
triton_machine.blue_machine[2]: Creating...
  cns.#:                "" => "1"
  cns.0.services.#:     "" => "2"
  cns.0.services.0:     "" => "happiness"
  cns.0.services.1:     "" => "blue-happiness"
  created:              "" => "<computed>"
  dataset:              "" => "<computed>"
  disk:                 "" => "<computed>"
  domain_names.#:       "" => "<computed>"
  firewall_enabled:     "" => "false"
  image:                "" => "b3045d03-75a4-4e3d-97e7-76b1a8de757a"
  ips.#:                "" => "<computed>"
  memory:               "" => "<computed>"
  name:                 "" => "blue_happy_3"
  networks.#:           "" => "1"
  networks.0:           "" => "31428241-4878-47d6-9fba-9a8436b596a4"
  nic.#:                "" => "<computed>"
  package:              "" => "g4-highcpu-128M"
  primaryip:            "" => "<computed>"
  root_authorized_keys: "" => "<computed>"
  type:                 "" => "<computed>"
  updated:              "" => "<computed>"
triton_machine.blue_machine[0]: Creating...
  cns.#:                "" => "1"
  cns.0.services.#:     "" => "2"
  cns.0.services.0:     "" => "happiness"
  cns.0.services.1:     "" => "blue-happiness"
  created:              "" => "<computed>"
  dataset:              "" => "<computed>"
  disk:                 "" => "<computed>"
  domain_names.#:       "" => "<computed>"
  firewall_enabled:     "" => "false"
  image:                "" => "b3045d03-75a4-4e3d-97e7-76b1a8de757a"
  ips.#:                "" => "<computed>"
  memory:               "" => "<computed>"
  name:                 "" => "blue_happy_1"
  networks.#:           "" => "1"
  networks.0:           "" => "31428241-4878-47d6-9fba-9a8436b596a4"
  nic.#:                "" => "<computed>"
  package:              "" => "g4-highcpu-128M"
  primaryip:            "" => "<computed>"
  root_authorized_keys: "" => "<computed>"
  type:                 "" => "<computed>"
  updated:              "" => "<computed>"
triton_machine.blue_machine[1]: Creating...
  cns.#:                "" => "1"
  cns.0.services.#:     "" => "2"
  cns.0.services.0:     "" => "happiness"
  cns.0.services.1:     "" => "blue-happiness"
  created:              "" => "<computed>"
  dataset:              "" => "<computed>"
  disk:                 "" => "<computed>"
  domain_names.#:       "" => "<computed>"
  firewall_enabled:     "" => "false"
  image:                "" => "b3045d03-75a4-4e3d-97e7-76b1a8de757a"
  ips.#:                "" => "<computed>"
  memory:               "" => "<computed>"
  name:                 "" => "blue_happy_2"
  networks.#:           "" => "1"
  networks.0:           "" => "31428241-4878-47d6-9fba-9a8436b596a4"
  nic.#:                "" => "<computed>"
  package:              "" => "g4-highcpu-128M"
  primaryip:            "" => "<computed>"
  root_authorized_keys: "" => "<computed>"
  type:                 "" => "<computed>"
  updated:              "" => "<computed>"

triton_machine.blue_machine[1]: Creation complete after 46s (ID: 3f7e2689-07fb-45f0-bd71-953e3f90877d)
triton_machine.blue_machine[2]: Creation complete after 46s (ID: 5bf6052f-0823-e558-de5b-c5b31b404245)
triton_machine.blue_machine[0]: Creation complete after 57s (ID: 63590c9a-e9e5-60ed-d959-8cf3992c5d29)

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

To see all of your instances, execute triton ls:

$ triton ls
SHORTID   NAME                  IMG                     STATE    FLAGS  AGE
3f7e2689  blue_happy_2          happy_randomizer@1.0.0  running  -      1m
5bf6052f  blue_happy_3          happy_randomizer@1.0.0  running  -      1m
63590c9a  blue_happy_1          happy_randomizer@1.0.0  running  -      1m

Step 4: building the green image

Now, we're ready to create version 1.1.0 of our application. Follow the instructions in the Happy Randomizer repo to create the image for happy_randomizer 1.1.0. This will be our green image.

Be sure to deploy it to your default data center (which you set in step 1), where version 1.0.0 lives.

Again, if your default environment is not the same data center as referred to in your Terraform files, be sure to declare the same profile in your new Packer image configuration.

Step 5: creating green application and infrastructure

To add green infrastructure to our Terraform deployment, first we must edit variables.tf. Using the text editor of your choice, add the following content to the file:

#
# Details about the "green" deployment
#
variable "green_image_name" {
    type = "string"
    description = "The name of the image for the 'green' deployment."
    default = "happy_randomizer"
}

variable "green_image_version" {
    type = "string"
    description = "The version of the image for the 'green' deployment."
    default = "1.1.0"
}

variable "green_count" {
    type = "string"
    description = "The number of 'green' instances to create."
    default = "3"
}

variable "green_package_name" {
    type = "string"
    description = "The package to use when making a green deployment."
    default = "g4-highcpu-128M" 
}

This declares the green image name, version, number of instances to create, and package.

NOTE: If your application requires a certain number of instances to always be up and running, be aware that Terraform often deletes before it creates. For example, if you need a minimum of three instances, you should deploy four instances. This way, when one instance is deleted, there's still three running. Once you're satisfied the application has been updated successfully and the changes are live, you can remove the extra instance.

You also must edit main.tf to create the instances. Add the following content to the end of that file:

#
# Details about the "green" deployment
#
data "triton_image" "green_image" {
    name = "${var.green_image_name}"
    version = "${var.green_image_version}"
    type = "lx-dataset"
    most_recent = true
}

resource "triton_machine" "green_machine" {
    count = "${var.green_count}"
    name = "green_happy_${count.index + 1}"
    package = "${var.green_package_name}"
    image = "${data.triton_image.green_image.id}"
    networks = ["${data.triton_network.service_networks.*.id}"]
    cns {
        services = ["${var.service_production == "green" ? var.service_name : "staging-${var.service_name}" }", "green-${var.service_name}"]
    }
}

This will take our new variables to create green instances.

Planning green infrastructure

Now that your files have been updated to include the green instances, run terraform plan to review the deployment. The result should look similar to the following:

$ terraform plan -out=bluegreenplan 
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.triton_image.green_image: Refreshing state...
data.triton_image.blue_image: Refreshing state...
data.triton_network.service_networks: Refreshing state...
triton_machine.blue_machine[2]: Refreshing state... (ID: 5bf6052f-0823-e558-de5b-c5b31b404245)
triton_machine.blue_machine[1]: Refreshing state... (ID: 3f7e2689-07fb-45f0-bd71-953e3f90877d)
triton_machine.blue_machine[0]: Refreshing state... (ID: 63590c9a-e9e5-60ed-d959-8cf3992c5d29)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + triton_machine.green_machine[0]
      id:                   <computed>
      cns.#:                "1"
      cns.0.services.#:     "2"
      cns.0.services.0:     "staging-happiness"
      cns.0.services.1:     "green-happiness"
      created:              <computed>
      dataset:              <computed>
      disk:                 <computed>
      domain_names.#:       <computed>
      firewall_enabled:     "false"
      image:                "bb767cc6-124b-40b1-baf7-1e23564a2e5a"
      ips.#:                <computed>
      memory:               <computed>
      name:                 "green_happy_1"
      networks.#:           "1"
      networks.0:           "31428241-4878-47d6-9fba-9a8436b596a4"
      nic.#:                <computed>
      package:              "g4-highcpu-128M"
      primaryip:            <computed>
      root_authorized_keys: <computed>
      type:                 <computed>
      updated:              <computed>

  + triton_machine.green_machine[1]
      id:                   <computed>
      cns.#:                "1"
      cns.0.services.#:     "2"
      cns.0.services.0:     "staging-happiness"
      cns.0.services.1:     "green-happiness"
      created:              <computed>
      dataset:              <computed>
      disk:                 <computed>
      domain_names.#:       <computed>
      firewall_enabled:     "false"
      image:                "bb767cc6-124b-40b1-baf7-1e23564a2e5a"
      ips.#:                <computed>
      memory:               <computed>
      name:                 "green_happy_2"
      networks.#:           "1"
      networks.0:           "31428241-4878-47d6-9fba-9a8436b596a4"
      nic.#:                <computed>
      package:              "g4-highcpu-128M"
      primaryip:            <computed>
      root_authorized_keys: <computed>
      type:                 <computed>
      updated:              <computed>

  + triton_machine.green_machine[2]
      id:                   <computed>
      cns.#:                "1"
      cns.0.services.#:     "2"
      cns.0.services.0:     "staging-happiness"
      cns.0.services.1:     "green-happiness"
      created:              <computed>
      dataset:              <computed>
      disk:                 <computed>
      domain_names.#:       <computed>
      firewall_enabled:     "false"
      image:                "bb767cc6-124b-40b1-baf7-1e23564a2e5a"
      ips.#:                <computed>
      memory:               <computed>
      name:                 "green_happy_3"
      networks.#:           "1"
      networks.0:           "31428241-4878-47d6-9fba-9a8436b596a4"
      nic.#:                <computed>
      package:              "g4-highcpu-128M"
      primaryip:            <computed>
      root_authorized_keys: <computed>
      type:                 <computed>
      updated:              <computed>

Plan: 3 to add, 0 to change, 0 to destroy.

This plan was saved to: bluegreenplan

To perform exactly these actions, run the following command to apply:
    terraform apply "bluegreenplan"

The new plan is creating the three new green instances. The blue instances will remain untouched.

Applying to add green infrastructure

Create the green instances with terraform apply with our plan name, bluegreenplan.

$ terraform apply "bluegreenplan"
triton_machine.green_machine[1]: Creating...
  cns.#:                "" => "1"
  cns.0.services.#:     "" => "2"
  cns.0.services.0:     "" => "staging-happiness"
  cns.0.services.1:     "" => "green-happiness"
  created:              "" => "<computed>"
  dataset:              "" => "<computed>"
  disk:                 "" => "<computed>"
  domain_names.#:       "" => "<computed>"
  firewall_enabled:     "" => "false"
  image:                "" => "bb767cc6-124b-40b1-baf7-1e23564a2e5a"
  ips.#:                "" => "<computed>"
  memory:               "" => "<computed>"
  name:                 "" => "green_happy_2"
  networks.#:           "" => "1"
  networks.0:           "" => "31428241-4878-47d6-9fba-9a8436b596a4"
  nic.#:                "" => "<computed>"
  package:              "" => "g4-highcpu-128M"
  primaryip:            "" => "<computed>"
  root_authorized_keys: "" => "<computed>"
  type:                 "" => "<computed>"
  updated:              "" => "<computed>"
triton_machine.green_machine[2]: Creating...
  cns.#:                "" => "1"
  cns.0.services.#:     "" => "2"
  cns.0.services.0:     "" => "staging-happiness"
  cns.0.services.1:     "" => "green-happiness"
  created:              "" => "<computed>"
  dataset:              "" => "<computed>"
  disk:                 "" => "<computed>"
  domain_names.#:       "" => "<computed>"
  firewall_enabled:     "" => "false"
  image:                "" => "bb767cc6-124b-40b1-baf7-1e23564a2e5a"
  ips.#:                "" => "<computed>"
  memory:               "" => "<computed>"
  name:                 "" => "green_happy_3"
  networks.#:           "" => "1"
  networks.0:           "" => "31428241-4878-47d6-9fba-9a8436b596a4"
  nic.#:                "" => "<computed>"
  package:              "" => "g4-highcpu-128M"
  primaryip:            "" => "<computed>"
  root_authorized_keys: "" => "<computed>"
  type:                 "" => "<computed>"
  updated:              "" => "<computed>"
triton_machine.green_machine[0]: Creating...
  cns.#:                "" => "1"
  cns.0.services.#:     "" => "2"
  cns.0.services.0:     "" => "staging-happiness"
  cns.0.services.1:     "" => "green-happiness"
  created:              "" => "<computed>"
  dataset:              "" => "<computed>"
  disk:                 "" => "<computed>"
  domain_names.#:       "" => "<computed>"
  firewall_enabled:     "" => "false"
  image:                "" => "bb767cc6-124b-40b1-baf7-1e23564a2e5a"
  ips.#:                "" => "<computed>"
  memory:               "" => "<computed>"
  name:                 "" => "green_happy_1"
  networks.#:           "" => "1"
  networks.0:           "" => "31428241-4878-47d6-9fba-9a8436b596a4"
  nic.#:                "" => "<computed>"
  package:              "" => "g4-highcpu-128M"
  primaryip:            "" => "<computed>"
  root_authorized_keys: "" => "<computed>"
  type:                 "" => "<computed>"
  updated:              "" => "<computed>"

triton_machine.green_machine[1]: Creation complete after 46s (ID: 7db3429a-e20c-ca47-dd2e-cc0e84a24e5e)
triton_machine.green_machine[2]: Creation complete after 46s (ID: 6c623e78-d879-e17c-b582-f8b5bf130a11)
triton_machine.green_machine[0]: Creation complete after 58s (ID: 48abac15-a182-c904-fd42-8c85b5492c01)

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

At this point in time, you should have three blue instances and three green instances. List all of your instances with triton ls:

$ triton ls
SHORTID   NAME                  IMG                     STATE    FLAGS  AGE
3f7e2689  blue_happy_2          happy_randomizer@1.0.0  running  -      11m
5bf6052f  blue_happy_3          happy_randomizer@1.0.0  running  -      11m
63590c9a  blue_happy_1          happy_randomizer@1.0.0  running  -      10m
6c623e78  green_happy_3         happy_randomizer@1.1.0  running  -      1m
7db3429a  green_happy_2         happy_randomizer@1.1.0  running  -      1m
48abac15  green_happy_1         happy_randomizer@1.1.0  running  -      1m

Step 6: checking for errors

It's important to make sure that our newly updated Happy Randomizer is running as expected. I'll get the DNS name for green_happy_1 to view the application.

$ triton inst get green_happy_1
{
    "id": "48abac15-a182-c904-fd42-8c85b5492c01",
    "name": "green_happy_1",
    "type": "smartmachine",
    "brand": "lx",
    "state": "running",
    "image": "bb767cc6-124b-40b1-baf7-1e23564a2e5a",
    "ips": [
        "165.225.173.146"
    ],
    [...]
    "dns_names": [
        "48abac15-a182-c904-fd42-8c85b5492c01.inst.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone",
        "green-happy-1.inst.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone",
        "staging-happiness.svc.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone",
        "green-happiness.svc.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone"
    ]
}

I visited green-happy-1.inst.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone, and I'm satisfied that the new GIFs are in place. To see other information associated with your deployment, use terraform show to view all of the values available to you.

Step 7: point production DNS name to green instances

At this point, we can begin to consider updating a load balancer or vanity domain name via a CNAME update. My application was pointed to happy.alexandra.space.

In variables.tf, the production variable has a default value set to blue. Now that we're certain the application is working as expected, update that value to be set as green:

variable "service_production" {
    type = "string"
    description = "Which deployment is considered 'production'? The other is 'staging'. Value can be one of 'blue' or 'green'."
    default = "green"
}

After our next Terraform run, this will update CNS to remove staging- from the green instances' DNS names and add it to the blue instances' DNS names. Once applied, the DNS names for your instances will be as follows:

  • Blue instances: staging-happiness.svc.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone
  • Green instances: happiness.svc.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone

Read more about Triton CNS and vanity URLs.

Planning the updated CNS names

Run terraform plan to check that the appropriate changes will be made.

$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.triton_image.blue_image: Refreshing state...
data.triton_network.service_networks: Refreshing state...
data.triton_image.green_image: Refreshing state...
triton_machine.green_machine[1]: Refreshing state... (ID: 7db3429a-e20c-ca47-dd2e-cc0e84a24e5e)
triton_machine.blue_machine[1]: Refreshing state... (ID: 3f7e2689-07fb-45f0-bd71-953e3f90877d)
triton_machine.green_machine[0]: Refreshing state... (ID: 48abac15-a182-c904-fd42-8c85b5492c01)
triton_machine.green_machine[2]: Refreshing state... (ID: 6c623e78-d879-e17c-b582-f8b5bf130a11)
triton_machine.blue_machine[2]: Refreshing state... (ID: 5bf6052f-0823-e558-de5b-c5b31b404245)
triton_machine.blue_machine[0]: Refreshing state... (ID: 63590c9a-e9e5-60ed-d959-8cf3992c5d29)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  ~ triton_machine.blue_machine[0]
      cns.0.services.0: "happiness" => "staging-happiness"

  ~ triton_machine.blue_machine[1]
      cns.0.services.0: "happiness" => "staging-happiness"

  ~ triton_machine.blue_machine[2]
      cns.0.services.0: "happiness" => "staging-happiness"

  ~ triton_machine.green_machine[0]
      cns.0.services.0: "staging-happiness" => "happiness"

  ~ triton_machine.green_machine[1]
      cns.0.services.0: "staging-happiness" => "happiness"

  ~ triton_machine.green_machine[2]
      cns.0.services.0: "staging-happiness" => "happiness"

Plan: 0 to add, 6 to change, 0 to destroy.

Applying the updated CNS names

Once you're satisfied the information is correct, run terraform apply. When prompted with Do you want to perform these actions?, enter yes.

Remember that new DNS names often do not propagate immediately. You can use dig to confirm that the correct IP addresses are pointed at your domain:

$ dig +noall +answer happy.alexandra.space

happy.alexandra.space.  1764    IN  CNAME   happiness.svc.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone.
happiness.svc.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone. 30 IN A165.225.159.240
happiness.svc.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone. 30 IN A165.225.158.220
happiness.svc.d9a01feb-be7d-6a32-b58d-ec4a2bf4ba7d.us-sw-1.triton.zone. 30 IN A165.225.159.196

Step 8: (optional) decommission the blue instances

It's now safe to decommission the blue instances. I'll go into variables.tf to change blue_count to 0:

variable "blue_count" {
    type = "string"
    description = "The number of 'blue' instances to create."
    default = "0"
}

This is optional because the blue instances now have a staging DNS name. If you don't want anyone to have access to the older version, or you're concerned about the additional cost of running both simultaneously, proceed to plan this update. Otherwise, skip to the wrap up.

Planning the updated infrastructure

Let's make sure that the infrastructure will be properly updated with terraform plan:

$ terraform plan -out=greenplan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

data.triton_image.green_image: Refreshing state...
data.triton_image.blue_image: Refreshing state...
data.triton_network.service_networks: Refreshing state...
triton_machine.green_machine[0]: Refreshing state... (ID: 48abac15-a182-c904-fd42-8c85b5492c01)
triton_machine.green_machine[2]: Refreshing state... (ID: 6c623e78-d879-e17c-b582-f8b5bf130a11)
triton_machine.green_machine[1]: Refreshing state... (ID: 7db3429a-e20c-ca47-dd2e-cc0e84a24e5e)
triton_machine.blue_machine[1]: Refreshing state... (ID: 3f7e2689-07fb-45f0-bd71-953e3f90877d)
triton_machine.blue_machine: Refreshing state... (ID: 63590c9a-e9e5-60ed-d959-8cf3992c5d29)
triton_machine.blue_machine[2]: Refreshing state... (ID: 5bf6052f-0823-e558-de5b-c5b31b404245)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  - triton_machine.blue_machine
  - triton_machine.blue_machine[1]
  - triton_machine.blue_machine[2]

Plan: 0 to add, 0 to change, 3 to destroy.

This plan was saved to: greenplan

To perform exactly these actions, run the following command to apply:
    terraform apply "greenplan"

Now that we have confirmed just the blue instances will be removed, we can set it into motion.

Applying the updated infrastructure

Let's officially remove the blue instances with the greenplan.

$ terraform apply "greenplan"
triton_machine.blue_machine[2]: Destruction complete after 21s
triton_machine.blue_machine: Destruction complete after 21s
triton_machine.blue_machine[1]: Destruction complete after 21s

Apply complete! Resources: 0 added, 0 changed, 3 destroyed.

Check in on the new list of instances. If you're still in the Triton environment, you can skip setting it again.

eval "$(triton env)"

List all of your instances with triton ls:

$ triton ls
SHORTID   NAME                  IMG                     STATE    FLAGS  AGE
6c623e78  green_happy_3         happy_randomizer@1.1.0  running  -      11m
7db3429a  green_happy_2         happy_randomizer@1.1.0  running  -      11m
48abac15  green_happy_1         happy_randomizer@1.1.0  running  -      11m

The Happy Randomizer has officially been updated to version 1.1.0.

Alternatively, use terraform show to inspect the current state of your instances.

Wrapping up

Step-by-step review of blue-green deployment

We just went through eight steps for a full blue-green deployment. Not every step was directly related to instance creation/deletion. Some steps, like step 1, were essential preambles in order to successfully implement the deployment.

Check out a live version of the happiness randomizer. You can also download the Terraform files for this demo in full or sign up to watch our webinar.

There are a number of use cases for blue-green deployments, this was just one example. Go forth with your new knowledge of how to create a Terraform configuration file geared towards automating updates to your applications.