paint-brush
Unlocking IaC Part 3: Your First Terraform Deployment!by@chrisray
548 reads
548 reads

Unlocking IaC Part 3: Your First Terraform Deployment!

by Chris RaySeptember 30th, 2023
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

Getting setup to build with Terraform is hard! In Part 3 we get down to business and build a Splunk server in AWS using Terraform.
featured image - Unlocking IaC Part 3: Your First Terraform Deployment!
Chris Ray HackerNoon profile picture


Let’s have some fun!

Let’s face it: a 3 part series on something technical like IaC can be a challenge. I am sure you have been pulled away from this a handful of times by now. Making it to part 3 is exciting - it’s when the fun part begins. It’s the rubber hits the road moment. If you feel like you are barely hanging on and doubt you can finish this part, it’s probably good to know that most people feel like that, too. Very few walk into something as abstract as this and nail it on the first try (or third). Let’s get down to business!

For this first deployment, let’s use something we all know by name, Splunk. In this script, we will deploy a single server that has Splunk already installed and configured. This will use an Amazon Machine Image (AMI). There are many AMIs to pick from. Some are different OS, like Windows or Linux; some are different distros of Linux, like Ubuntu, CentOS, etc., and some are prebuilt applications like this one.


We will spin up the Splunk EC2 server (the "Splunk instance"), create network access to allow only your public IP address to reach it on TCP port 8000 (the default Splunk UI port), and then destroy all this with a simple Terraform command when we are done.


I will break the single terraform file (main.tf) into a few small chunks so we can more easily walk through each part. Then, at the end, I will put it all together so we can execute it.

How to figure things out on your own in Terraform land

Before we get started, it’s important that you know where you can go to find answers to questions I haven't considered here. As an example, when I started building in Terraform, I wondered, "How do I know what arguments (this is how you define the resources, like the type of virtual machine in an EC2 instance or the ingress protocols allowed in a security group). In this example, and for MANY other questions not answered here, you can go to the Terraform registry to learn more about available providers, resources, arguments, and syntax.


For example, in the registry, I can go and find the "Security Group" resource (that we will use in this tutorial), then go to the Inputs tab to view all available arguments (they call them variables, FYI) that will be allowed when we configure the resource. Okay...detour complete, onto the config of your first TF deployment!

Building with Terraform

First step, we need to define the provider we will want to use. We are working in AWS, so the provider will be "AWS.” We will also define the region we want to work out here as well.


provider "aws" {
  region = "us-west-1"
}


If you remember, I said we were going to use an Amazing Machine Image (AMI) for a virtual machine (EC2). Here, we use the Terraform "data" keyword to go and search the resource "aws_ami.” In addition, we give whatever data is returned as part of this search a name - "Splunk." This is so we can reference this data elsewhere in our code (keep this in mind later)...


Since searching anything requires some way to limit the search results only to what we want to find, we use the filter keyword and then pass it two arguments, "name" and "values.” Here, we instruct the search of "aws_ami" to search the field "name" for anything that starts with "Splunk." While this could need to be tuned to be more specific, in this case, this filter is good.


data "aws_ami" "splunk" {
  most_recent = true

  filter {
    name   = "name"
    values = ["splunk*"]
  }

}


In AWS, network access to resources is often controlled through a resource called a "Security Group.” In our example, we will be using a security group that allows TCP port 8000 inbound ("ingress") from my IP address (or yours, when you create this script). Because we are using Splunk in a private account with nonsensitive info, I will allow it to communicate outbound ("egress"). However, if this were a production environment, you would need to identify necessary IP & port ranges and restrict access to only them to follow best practices.


Notice I define the ingress parameters, including the protocol and "cidr_blocks.” This is important, as misconfigurations here can open up an environment accidentally to the broader internet. If you are interested in learning about providing additional network security for your AWS environment, let me know - I can create a tutorial on adding an Application Load Balancer (ALB) to this configuration to provide additional security features.


resource "aws_security_group" "splunksg" {
  name        = "SplunkSecGroup"
  description = "Allow-on-port-8000"

  ingress {
    from_port   = 8000
    to_port     = 8000
    protocol    = "tcp"
    cidr_blocks = ["YOUR_IP_HERE"] # Replace 'YOUR_PUBLIC_HERE' with your public IP address
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}


Finally, the reason we are all here - the virtual machine that will run Splunk. As is the case with all other portions of the Terraform code, we start by defining a resource, in this case, "aws_instance," and naming it "splunk_ec2," then go on to define other parameters below it using arguments.


Remember the "data" keyword we used above to search the "aws_ami" for the most recent "Splunk*" AMI? We have the name "Splunk". Here, in the EC2 instance configuration, we can reference the data contained in that query. We reference it by using the "ami" argument and saying it equals whatever is in "data.aws_ami.splunk.id". This is a really important concept to understand, so let’s spend two minutes breaking it down:


  1. "data.aws_ami." is referencing the data keyword in the block of code we created above.
  2. "splunk.id" references the name we gave this data, "Splunk," while the last piece, "id," references the AMI id that is returned by the "data" query. The AMI ID is a string that isn't easily remembered by humans but is unique and easy to use for machines.


Moving down, we say the security_group for this EC2 is the "splunksg" security group we defined above. This uses the same logic as the AMI "data" naming. I.E., it starts with the resource name, the object name we defined, and then the variable ".name," which we also defined as "SplunkSecGroup,” Also note, you can use the instance_type of t2.micro for this, and it works - it’s just very slow, so I opt for a t2.medium.


Last, we apply tags to this instance, which is helpful when an AWS account grows and keeping track of assets becomes more difficult.


resource "aws_instance" "splunk_ec2" {
  ami           = data.aws_ami.splunk.id
  instance_type = "t2.medium"

  security_groups = [aws_security_group.splunksg.name]

  tags = {
    Name = "SplunkInstance"
  }
}


Now, I know you could have just skipped ahead to this part and plopped this into the file system and run it and felt at peace with the world since you "completed" the tutorial, but don't do that. Don't be that lazy. The value in taking the time to read and build with me is in the pain of making mistakes and the joy of fixing those mistakes.


Putting it all together, we should have a terraform script that looks like this:


provider "aws" {
  region = "us-west-1"
}

data "aws_ami" "splunk" {
  most_recent = true

  filter {
    name   = "name"
    values = ["splunk*"]
  }

}

resource "aws_security_group" "splunksg" {
  name        = "SplunkSecGroup"
  description = "Allow-on-port-8000"

  ingress {
    from_port   = 8000
    to_port     = 8000
    protocol    = "tcp"
    cidr_blocks = ["YOUR_PUBLIC_HERE/32"] # Replace 'YOUR_PUBLIC_HERE' with your public IP address
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "splunk_ec2" {
  ami           = data.aws_ami.splunk.id
  instance_type = "t2.medium"

  security_groups = [aws_security_group.splunksg.name]

  tags = {
    Name = "SplunkInstance"
  }
}


Wow, look at that, 43 lines of code! It sounds like a lot, but you are done with it - you can update it, create with it, destroy it, or share it!


Because we have already set up all the required pieces to make this work in Part 2 of Unlocking IaC at this point, all that is needed is to save the file with the correct extension (let’s use main.tf).


Open up CMD or PowerShell as admin (CTRL+SHIFT+CLICK or right click and “run as…”), “CD” or change the directory into the same location as the file we just created (main.tf) and issue the following commands:

Terraform INIT


c:\Users\Chris\Documents\Projects\terraform\YT single server arch AWS>terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/aws v5.1.0

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.


Terraform init initializes Terraform in that directory. This is essentially starting up Terraform.


Terraform plan


c:\Users\Chris\Documents\Projects\terraform\YT single server arch AWS>terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.splunk_server will be created
  + resource "aws_instance" "splunk_server" {
      + ami                                  = "ami-0b5cb59327b8d7e1f"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = true
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t3.large"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags_all                             = (known after apply)
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id                 = (known after apply)
              + capacity_reservation_resource_group_arn = (known after apply)
            }
        }

      + cpu_options {
          + amd_sev_snp      = (known after apply)
          + core_count       = (known after apply)
          + threads_per_core = (known after apply)
        }

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + maintenance_options {
          + auto_recovery = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
          + instance_metadata_tags      = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_card_index    = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + private_dns_name_options {
          + enable_resource_name_dns_a_record    = (known after apply)
          + enable_resource_name_dns_aaaa_record = (known after apply)
          + hostname_type                        = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_security_group.splunk_server will be created
  + resource "aws_security_group" "splunk_server" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "[redacted]/32",
                ]
              + description      = ""
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
          + {
              + cidr_blocks      = [
                  + "[redacted]/32",
                ]
              + description      = ""
              + from_port        = 8000
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 8000
            },
        ]
      + name                   = (known after apply)
      + name_prefix            = "splunk-group"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags_all               = (known after apply)
      + vpc_id                 = (known after apply)
    }

  # aws_subnet.splunk_server will be created
  + resource "aws_subnet" "splunk_server" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = (known after apply)
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "172.31.1.0/28"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags_all                                       = (known after apply)
      + vpc_id                                         = "vpc-[redacted]"
    }

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


───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if
you run "terraform apply" now.

c:\Users\Chris\Documents\Projects\terraform\YT single server arch AWS>


Once that is completed it’s a good idea to run terraform plan which is a method to see what your Terraform build will do without actually making any changes to your cloud environment. Carefully review this output and make sure what you see matches what you expect.

Terraform apply


c:\Users\Chris\Documents\Projects\terraform\YT single server arch AWS>terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.splunk_server will be created
  + resource "aws_instance" "splunk_server" {
      + ami                                  = "ami-0b5cb59327b8d7e1f"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = true
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t3.large"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = (known after apply)
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags_all                             = (known after apply)
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id                 = (known after apply)
              + capacity_reservation_resource_group_arn = (known after apply)
            }
        }

      + cpu_options {
          + amd_sev_snp      = (known after apply)
          + core_count       = (known after apply)
          + threads_per_core = (known after apply)
        }

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (known after apply)
        }

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }

      + maintenance_options {
          + auto_recovery = (known after apply)
        }

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
          + instance_metadata_tags      = (known after apply)
        }

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_card_index    = (known after apply)
          + network_interface_id  = (known after apply)
        }

      + private_dns_name_options {
          + enable_resource_name_dns_a_record    = (known after apply)
          + enable_resource_name_dns_aaaa_record = (known after apply)
          + hostname_type                        = (known after apply)
        }

      + root_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

  # aws_security_group.splunk_server will be created
  + resource "aws_security_group" "splunk_server" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = (known after apply)
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "[redacted]/32",
                ]
              + description      = ""
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
          + {
              + cidr_blocks      = [
                  + "[redacted]/32",
                ]
              + description      = ""
              + from_port        = 8000
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 8000
            },
        ]
      + name                   = (known after apply)
      + name_prefix            = "splunk-group"
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags_all               = (known after apply)
      + vpc_id                 = (known after apply)
    }

  # aws_subnet.splunk_server will be created
  + resource "aws_subnet" "splunk_server" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = (known after apply)
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "172.31.1.0/28"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags_all                                       = (known after apply)
      + vpc_id                                         = "vpc-[redacted]"
    }

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

Changes to Outputs:
  + splunk_server_public_ip = (known after apply)

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_subnet.splunk_server: Creating...
aws_security_group.splunk_server: Creating...
aws_subnet.splunk_server: Creation complete after 1s [id=subnet-053c39ae3dcc97112]
aws_security_group.splunk_server: Creation complete after 2s [id=sg-03bf97ed2524e4aee]
aws_instance.splunk_server: Creating...
aws_instance.splunk_server: Still creating... [10s elapsed]
aws_instance.splunk_server: Creation complete after 13s [id=i-050c4bad2d2a6ba93]

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


c:\Users\Chris\Documents\Projects\terraform\YT single server arch AWS>


Finally, running terraform apply takes the code and applies it to your AWS account. This can take a few seconds up to a minute, but you should receive a success message once it’s done. If you get any errors, carefully review the error. Terraform errors are usually high quality and will give clues that don’t require much (if any) research to understand.

Splunk login

At this point, your Splunk server is probably ready to log into (it takes a minute or two for the UI to become available on Splunk). To log into Splunk, you will need three pieces of info:

  1. The username, which is admin
  2. The password, which will be SPLUNK-<instanceid>
  3. The IP address and port for this EC2 instance in AWS (e.g. http://10.10.10.10:8000)


While it is possible to extract the IP address from Terraform and have it delivered to you as an OUTPUT of the script or to use the AWS CLI to collect this info, we didn’t do that here for simplicity's sake. In order to get your IP address and the instance ID, you will need to log into your AWS console.


Search for EC2 in the services menu, then click EC2, then click Instances (running). In this view, you will see a list of running EC2 instances, which should be just the one unless you have other fun stuff going on.

Click the correct instance (or the only one), and in this view, you will see in the upper left “Instance ID,” which can be copied by clicking the copy button on the left, and to the right, you will see your IP address.


Take that IP address and, using your browser of choice, type in http://[instanceIP]:8000 then hit enter. You should see the Splunk login - if you don’t, wait another minute or two - Splunk takes (literally) 2-3 minutes to launch the first time. Remember the username = admin and the password = SPLUNK-[instanceID].


Terraform Destroy

Building is great, and learning new stuff is fun - forgetting to turn off your cloud resources when you are done having fun sucks. Terraform destroy performs the reverse of terraform apply. It tears down the infrastructure specified in the terraform file. To use terraform destroy, which you should right now, go back to your command line and enter terraform destroy . A successful run of terraform destroy will be noted by the lack of Splunk UI (it will probably notify you it’s disconnected) and also by the output of the destroy command.


c:\Users\Chris\Documents\Projects\terraform\YT single server arch AWS>terraform destroy
aws_subnet.splunk_server: Refreshing state... [id=subnet-053c39ae3dcc97112]
aws_security_group.splunk_server: Refreshing state... [id=sg-03bf97ed2524e4aee]
aws_instance.splunk_server: Refreshing state... [id=i-050c4bad2d2a6ba93]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_instance.splunk_server will be destroyed
  - resource "aws_instance" "splunk_server" {
      - ami                                  = "ami-0b5cb59327b8d7e1f" -> null
      - arn                                  = "arn:aws:ec2:us-west-2:625982356191:instance/i-050c4bad2d2a6ba93" -> null
      - associate_public_ip_address          = true -> null
      - availability_zone                    = "us-west-2b" -> null
      - cpu_core_count                       = 1 -> null
      - cpu_threads_per_core                 = 2 -> null
      - disable_api_stop                     = false -> null
      - disable_api_termination              = false -> null
      - ebs_optimized                        = false -> null
      - get_password_data                    = false -> null
      - hibernation                          = false -> null
      - id                                   = "i-050c4bad2d2a6ba93" -> null
      - instance_initiated_shutdown_behavior = "stop" -> null
      - instance_state                       = "running" -> null
      - instance_type                        = "t3.large" -> null
      - ipv6_address_count                   = 0 -> null
      - ipv6_addresses                       = [] -> null
      - monitoring                           = false -> null
      - placement_partition_number           = 0 -> null
      - primary_network_interface_id         = "eni-059f68aecb53fc4bb" -> null
      - private_dns                          = "ip-172-31-1-11.us-west-2.compute.internal" -> null
      - private_ip                           = "172.31.1.11" -> null
      - public_dns                           = "[redacted].us-west-2.compute.amazonaws.com" -> null
      - public_ip                            = "[redacted]" -> null
      - secondary_private_ips                = [] -> null
      - security_groups                      = [
          - "splunk-group20230929124520702900000001",
        ] -> null
      - source_dest_check                    = true -> null
      - subnet_id                            = "subnet-053c39ae3dcc97112" -> null
      - tags                                 = {} -> null
      - tags_all                             = {} -> null
      - tenancy                              = "default" -> null
      - user_data_replace_on_change          = false -> null
      - vpc_security_group_ids               = [
          - "sg-03bf97ed2524e4aee",
        ] -> null

      - capacity_reservation_specification {
          - capacity_reservation_preference = "open" -> null
        }

      - cpu_options {
          - core_count       = 1 -> null
          - threads_per_core = 2 -> null
        }

      - credit_specification {
          - cpu_credits = "unlimited" -> null
        }

      - enclave_options {
          - enabled = false -> null
        }

      - maintenance_options {
          - auto_recovery = "default" -> null
        }

      - metadata_options {
          - http_endpoint               = "enabled" -> null
          - http_put_response_hop_limit = 1 -> null
          - http_tokens                 = "optional" -> null
          - instance_metadata_tags      = "disabled" -> null
        }

      - private_dns_name_options {
          - enable_resource_name_dns_a_record    = false -> null
          - enable_resource_name_dns_aaaa_record = false -> null
          - hostname_type                        = "ip-name" -> null
        }

      - root_block_device {
          - delete_on_termination = true -> null
          - device_name           = "/dev/xvda" -> null
          - encrypted             = false -> null
          - iops                  = 100 -> null
          - tags                  = {} -> null
          - throughput            = 0 -> null
          - volume_id             = "vol-0e283fb892f5fe38a" -> null
          - volume_size           = 20 -> null
          - volume_type           = "gp2" -> null
        }
    }

  # aws_security_group.splunk_server will be destroyed
  - resource "aws_security_group" "splunk_server" {
      - arn                    = "arn:aws:ec2:us-west-2:625982356191:security-group/sg-03bf97ed2524e4aee" -> null
      - description            = "Managed by Terraform" -> null
      - egress                 = [] -> null
      - id                     = "sg-03bf97ed2524e4aee" -> null
      - ingress                = [
          - {
              - cidr_blocks      = [
                  - "[redacted]/32",
                ]
              - description      = ""
              - from_port        = 22
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 22
            },
          - {
              - cidr_blocks      = [
                  - "[redacted]/32",
                ]
              - description      = ""
              - from_port        = 8000
              - ipv6_cidr_blocks = []
              - prefix_list_ids  = []
              - protocol         = "tcp"
              - security_groups  = []
              - self             = false
              - to_port          = 8000
            },
        ] -> null
      - name                   = "splunk-group20230929124520702900000001" -> null
      - name_prefix            = "splunk-group" -> null
      - owner_id               = "625982356191" -> null
      - revoke_rules_on_delete = false -> null
      - tags                   = {} -> null
      - tags_all               = {} -> null
      - vpc_id                 = "vpc-002c2876364b2b282" -> null
    }

  # aws_subnet.splunk_server will be destroyed
  - resource "aws_subnet" "splunk_server" {
      - arn                                            = "arn:aws:ec2:us-west-2:625982356191:subnet/subnet-053c39ae3dcc97112" -> null
      - assign_ipv6_address_on_creation                = false -> null
      - availability_zone                              = "us-west-2b" -> null
      - availability_zone_id                           = "usw2-az1" -> null
      - cidr_block                                     = "172.31.1.0/28" -> null
      - enable_dns64                                   = false -> null
      - enable_lni_at_device_index                     = 0 -> null
      - enable_resource_name_dns_a_record_on_launch    = false -> null
      - enable_resource_name_dns_aaaa_record_on_launch = false -> null
      - id                                             = "subnet-053c39ae3dcc97112" -> null
      - ipv6_native                                    = false -> null
      - map_customer_owned_ip_on_launch                = false -> null
      - map_public_ip_on_launch                        = false -> null
      - owner_id                                       = "625982356191" -> null
      - private_dns_hostname_type_on_launch            = "ip-name" -> null
      - tags                                           = {} -> null
      - tags_all                                       = {} -> null
      - vpc_id                                         = "vpc-002c2876364b2b282" -> null
    }

Plan: 0 to add, 0 to change, 3 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.

  Enter a value: yes

aws_instance.splunk_server: Destroying... [id=i-050c4bad2d2a6ba93]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 10s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 20s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 30s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 40s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 51s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 1m1s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 1m11s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 1m21s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 1m31s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 1m41s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 1m51s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 2m1s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 2m11s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 2m21s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 2m31s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 2m41s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 2m51s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 3m1s elapsed]
aws_instance.splunk_server: Still destroying... [id=i-050c4bad2d2a6ba93, 3m11s elapsed]
aws_instance.splunk_server: Destruction complete after 3m14s
aws_subnet.splunk_server: Destroying... [id=subnet-053c39ae3dcc97112]
aws_security_group.splunk_server: Destroying... [id=sg-03bf97ed2524e4aee]
aws_subnet.splunk_server: Destruction complete after 0s
aws_security_group.splunk_server: Destruction complete after 0s

Destroy complete! Resources: 3 destroyed.

c:\Users\Chris\Documents\Projects\terraform\YT single server arch AWS>


Making it to the end of a monster tutorial like this ain’t easy. Congrats if you made it this far! If I missed something, which is likely, sorry! At this point, it would be worthwhile to keep toying with this newfound skill - change up your terraform script and add an EBS volume to the EC2 instance for additional storage. Add S3 for long-term storage. Figure out how to automatically add an SSH key pair to the VM so you can SSH into it and use the UI. The main thing is to keep playing around with this every day, even a little bit.


The moment you stop is the moment you start to lose this skill slowly.