DEV Community

matt from bitLeaf.io
matt from bitLeaf.io

Posted on • Originally published at bitleaf.io on

Creating a DigitalOcean Droplet with Terraform - Part 2 of 3

Creating a DigitalOcean Droplet with Terraform - Part 2 of 3

Welcome to Part 2 of our 3 part series post on creating a DigitalOcean droplet with Terraform. Please check out Part 1 of Creating a DigitalOcean droplet with Terraform before continuing.

In Part 1 we setup our Terraform configuration files. Now in Part 2 we are going to use Terraform to commit those changes and create the objects on DigitalOcean.

Note: This will create objects in DigitalOcean and you will get charged for any uptime. However at the end of this example we will destroy all the objects that we created, so you shouldn't be left with anything that will get charged ongoing.

Make sure you have downloaded Terraform for your platform. It's a single executable, so it makes it simple with no install. These posts are based on Terraform v0.12.

Open up your shell/command prompt and change over to the directory with your Terraform files. First we are going to initialize things by bringing down the necessary information to work with our DigitalOcean provider. This will pull down the necessary files to a local '.terraform' directory.

terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "digitalocean" (terraform-providers/digitalocean) 1.16.0...

...remaining text left out for brevity...

Terraform init command

Now that our provider information has been pulled down, we need to tell Terraform to stage our setup. We do that by calling 'terraform plan'. Plan will check over our configuration files, making sure there are no mistakes, and then let us know what it will do when we finally commit this. It's also good to use the '-out' switch with 'terraform plan' so it outputs the plan and you can re-run that exact setup again using that later in the future. That will let you create an exact copy of whatever you just setup.

Note: If you get prompted for your DigitalOcean credentials, make sure to go back to Part 1 of these posts and export/set your environment variables again in the current shell/command prompt.

terraform plan -out droplet_volume.tfplan
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.

------------------------------------------------------------------------

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:

  # digitalocean_droplet.bitleaf_server_1 will be created
  + resource "digitalocean_droplet" "bitleaf_server_1" {
      + backups = false
      + created_at = (known after apply)
      + disk = (known after apply)
      + id = (known after apply)
      + image = "ubuntu-18-04-x64"
      + ipv4_address = (known after apply)
      + ipv4_address_private = (known after apply)
      + ipv6 = false
      + ipv6_address = (known after apply)
      + ipv6_address_private = (known after apply)
      + locked = (known after apply)
      + memory = (known after apply)
      + monitoring = false
      + name = "bitleaf-server-1"
      + price_hourly = (known after apply)
      + price_monthly = (known after apply)
      + private_networking = false
      + region = "nyc3"
      + resize_disk = true
      + size = "s-1vcpu-2gb"
      + ssh_keys = [
          + "<redacted>",
        ]
      + status = (known after apply)
      + urn = (known after apply)
      + vcpus = (known after apply)
      + volume_ids = (known after apply)
      + vpc_uuid = (known after apply)
    }

  # digitalocean_volume.bitleaf_volume_1 will be created
  + resource "digitalocean_volume" "bitleaf_volume_1" {
      + description = "bitleaf volume 1"
      + droplet_ids = (known after apply)
      + filesystem_label = (known after apply)
      + filesystem_type = (known after apply)
      + id = (known after apply)
      + initial_filesystem_type = "ext4"
      + name = "biteaf-volume-1"
      + region = "nyc3"
      + size = 5
      + urn = (known after apply)
    }

  # digitalocean_volume_attachment.bitleaf_volume_1 will be created
  + resource "digitalocean_volume_attachment" "bitleaf_volume_1" {
      + droplet_id = (known after apply)
      + id = (known after apply)
      + volume_id = (known after apply)
    }

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

------------------------------------------------------------------------

This plan was saved to: droplet_volume.tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "droplet_volume.tfplan"

Terraform plan

So as you can see, Terraform is letting you know exactly what it plans to do. It also shows that there are some pieces of information like IP addresses that we'll have access to after the droplet has been created. We're using one of those to output our public IP address.

Now that Terraform has checked out configuration and let us know what it will be doing it's time to actually commit it and create the droplet and volume on DigitalOcean. We'll be using the 'terraform apply' command for that. We'll also be telling 'terraform apply' to use our plan file that we just generated. If you don't pass in the plan it will re-run the plan again.

terraform apply "droplet_volume.tfplan"
digitalocean_volume.bitleaf_volume_1: Creating...
digitalocean_droplet.bitleaf_server_1: Creating...
digitalocean_volume.bitleaf_volume_1: Creation complete after 7s
digitalocean_droplet.bitleaf_server_1: Still creating... [10s elapsed]
digitalocean_droplet.bitleaf_server_1: Still creating... [20s elapsed]
digitalocean_droplet.bitleaf_server_1: Still creating... [30s elapsed]
digitalocean_droplet.bitleaf_server_1: Creation complete after 33s
digitalocean_volume_attachment.bitleaf_volume_1: Creating...
digitalocean_volume_attachment.bitleaf_volume_1: Still creating... [10s elapsed]
digitalocean_volume_attachment.bitleaf_volume_1: Creation complete after 12s

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

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

Outputs:

public_ip_server = 123.456.78.9

Terraform apply

Terraform just went out and from a couple configuration files created a droplet, a volume, and attached the volume to the droplet. You'll notice that under 'Resources' that it has '3 added'. If you might recall, the attaching of the volume to the droplet was a Terraform resource type, so the three are that, the droplet, and the volume. It also nicely provided us with the public IP address to our new droplet. We could now just ssh into the droplet as root. It has our DigitalOcean SSH key so no password will be required.

So that's awesome. Now, let's run Terraform plan again and see what happens.

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.

digitalocean_volume.bitleaf_volume_1: Refreshing state... 
digitalocean_droplet.bitleaf_server_1: Refreshing state... 
digitalocean_volume_attachment.bitleaf_volume_1: Refreshing state... ]

------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

Terraform plan after apply

So what happened? The cool thing is Terraform keeps track of the state of your environment and what has been run through Terraform. It knows that you already created that particular droplet and volume. So let's change our configuration and add a second volume to our droplet.

# Specify the Terraform provider to use
provider "digitalocean" {
  token = var.do_token
}

# Setup a DO volume
resource "digitalocean_volume" "bitleaf_volume_1" {
  region = "nyc3"
  name = "biteaf-volume-1"
  size = 5
  initial_filesystem_type = "ext4"
  description = "bitleaf volume 1"
}

# Setup a second DO volume
resource "digitalocean_volume" "bitleaf_volume_2" {
  region = "nyc3"
  name = "biteaf-volume-2"
  size = 5
  initial_filesystem_type = "ext4"
  description = "bitleaf volume 2"
}

# Setup a DO droplet
resource "digitalocean_droplet" "bitleaf_server_1" {
  image = var.droplet_image
  name = "bitleaf-server-1"
  region = var.region
  size = var.droplet_size
  private_networking = var.private_networking
  ssh_keys = [
    var.ssh_key_fingerprint
  ]
  # user_data = data.template_file.cloud-init-yaml.rendered
}

# Connect the volume to the droplet
resource "digitalocean_volume_attachment" "bitleaf_volume_1" {
  droplet_id = digitalocean_droplet.bitleaf_server_1.id
  volume_id = digitalocean_volume.bitleaf_volume_1.id
}

# Connect the second volume to the droplet
resource "digitalocean_volume_attachment" "bitleaf_volume_2" {
  droplet_id = digitalocean_droplet.bitleaf_server_1.id
  volume_id = digitalocean_volume.bitleaf_volume_2.id
}

# Output the public IP address of the new droplet
 output "public_ip_server" {
  value = digitalocean_droplet.bitleaf_server_1.ipv4_address
}

Adding a second volume to our droplet

We now updated our configuration to simply add a second volume and to attach that second volume to the droplet. Now let's run 'terraform plan' and see what it says.

terraform plan -out droplet_volume2.tfplan
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.

digitalocean_volume.bitleaf_volume_1: Refreshing state... 
digitalocean_droplet.bitleaf_server_1: Refreshing state... 
digitalocean_volume_attachment.bitleaf_volume_1: 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:

  # digitalocean_volume.bitleaf_volume_2 will be created
  + resource "digitalocean_volume" "bitleaf_volume_2" {
      + description = "bitleaf volume 2"
      + droplet_ids = (known after apply)
      + filesystem_label = (known after apply)
      + filesystem_type = (known after apply)
      + id = (known after apply)
      + initial_filesystem_type = "ext4"
      + name = "biteaf-volume-2"
      + region = "nyc3"
      + size = 5
      + urn = (known after apply)
    }

  # digitalocean_volume_attachment.bitleaf_volume_2 will be created
  + resource "digitalocean_volume_attachment" "bitleaf_volume_2" {
      + droplet_id = 
      + id = (known after apply)
      + volume_id = (known after apply)
    }

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

------------------------------------------------------------------------

This plan was saved to: droplet_volume2.tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "droplet_volume2.tfplan"

Terraform plan after the update

Since Terraform keeps track of the state of the changes we made to our Terraform configurations and with what we have already run it has calculated the necessary changes it needs to make to our DigitalOcean infrastructure. In this case it will be adding two resources (a volume, and attaching that volume to the droplet). If we went ahead and run that it will now commit those updates to DigitalOcean.

Now that we ran through these examples let's go ahead and clean up after ourselves and remove the created droplet and volumes. This is done through the ominous sounding 'terraform destroy' command. Again, since Terraform keeps track of the state of our changes it knows what needs to be removed.

terraform destroy
digitalocean_volume.bitleaf_volume_1: Refreshing state...
digitalocean_volume.bitleaf_volume_2: Refreshing state... 
digitalocean_droplet.bitleaf_server_1: Refreshing state... 
digitalocean_volume_attachment.bitleaf_volume_1: Refreshing state... 
digitalocean_volume_attachment.bitleaf_volume_2: Refreshing state... 

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:

  # digitalocean_droplet.bitleaf_server_1 will be destroyed
  - resource "digitalocean_droplet" "bitleaf_server_1" {
      - backups = false -> null
      - created_at = "2020-04-25T02:43:05Z" -> null
      - disk = 50 -> null
      - id = "189890644" -> null
      - image = "ubuntu-18-04-x64" -> null
      - ipv4_address = "" -> null
      - ipv6 = false -> null
      - locked = false -> null
      - memory = 2048 -> null
      - monitoring = false -> null
      - name = "bitleaf-server-1" -> null
      - price_hourly = 0.01488 -> null
      - price_monthly = 10 -> null
      - private_networking = false -> null
      - region = "nyc3" -> null
      - resize_disk = true -> null
      - size = "s-1vcpu-2gb" -> null
      - ssh_keys = [
          - "",
        ] -> null
      - status = "active" -> null
      - tags = [] -> null
      - urn = "do:droplet:189890644" -> null
      - vcpus = 1 -> null
      - volume_ids = [
          - "",
          - "",
        ] -> null
    }

  # digitalocean_volume.bitleaf_volume_1 will be destroyed
  - resource "digitalocean_volume" "bitleaf_volume_1" {
      - description = "bitleaf volume 1" -> null
      - droplet_ids = [
          - 189890644,
        ] -> null
      - filesystem_type = "ext4" -> null
      - id = "" -> null
      - initial_filesystem_type = "ext4" -> null
      - name = "biteaf-volume-1" -> null
      - region = "nyc3" -> null
      - size = 5 -> null
      - tags = [] -> null
      - urn = "do:volume:" -> null
    }

  # digitalocean_volume.bitleaf_volume_2 will be destroyed
  - resource "digitalocean_volume" "bitleaf_volume_2" {
      - description = "bitleaf volume 2" -> null
      - droplet_ids = [
          - 189890644,
        ] -> null
      - filesystem_type = "ext4" -> null
      - id = "" -> null
      - initial_filesystem_type = "ext4" -> null
      - name = "biteaf-volume-2" -> null
      - region = "nyc3" -> null
      - size = 5 -> null
      - tags = [] -> null
      - urn = "do:volume:" -> null
    }

  # digitalocean_volume_attachment.bitleaf_volume_1 will be destroyed
  - resource "digitalocean_volume_attachment" "bitleaf_volume_1" {
      - droplet_id = -> null
      - id = "" -> null
      - volume_id = "" -> null
    }

  # digitalocean_volume_attachment.bitleaf_volume_2 will be destroyed
  - resource "digitalocean_volume_attachment" "bitleaf_volume_2" {
      - droplet_id = -> null
      - id = "" -> null
      - volume_id = "" -> null
    }

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

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

Terraform destroy

Awesome. Terraform knows we have a droplet and two volumes and now we can easily clear the state by removing those with the 'destroy' command. If we wanted to bring them back up again we can simply run the 'terraform apply' command pointing to our saved plan file.

So now we know how to automate the creation of things like droplets and volumes, but how do we do some customization of the operating system on the droplet. That is where a 'cloud-init' file comes into play and we can do it all during our Terraform setup. We'll cover that in Part 3 of Creating a DigitalOcean Droplet with Terraform.

Top comments (0)