Using Terraform to Deploy an OCI Compute Instance
Tags: oracle, cloud, terraform
Published: 2023-06-19 22:11:28

This is a continuation from my last article on [deploying a simple compartment](https://dev.to/farisdurrani/using-terraform-to-deploy-an-oci-compute-instance-597f) on the Oracle Cloud Infrastructure (OCI). 

As part of our effort to employ Infrastructure as Code (IaC), Terraform affords us one way to deploy cloud resources in a predictable and declarative method.

This is a guide to show you how to deploy an OCI Compute Instance from scratch using Terraform.

## Prerequisites

1\. Install Terraform. 

Follow the official [installation page](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) to install Terraform on your machine.

2\. Configure the OCI Provider

To deploy OCI resources, you need access to manage those resources from your machine. This can be achieved using an API Key. To complete this step, see [Setting up the OCI Configuration File using API Keys](https://dev.to/farisdurrani/setting-up-the-oci-configuration-file-using-api-keys-96c).

## How to deploy a Compute Instance using Terraform

### The architectural goal
Our goal is to deploy the following:

![Architectural goal of TF deployment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9cswnqn9y4qnuvcwf4js.png)

It is impossible to deploy only a Compute Instance since it depends on a network (Virtual Cloud Network) to be usable. Preferably, we want to SSH into the new instance so we need an Internet Gateway as well. All of that will be made in a new compartment in the Ashburn region.

### The file structure goal

Our final file structure goal. We work in the new directory `tf-test-1/`: 
```txt
📦tf-test-1
 ┣ 📜id_rsa
 ┣ 📜id_rsa.pub
 ┣ 📜terraform.tf
 ┣ 📜provider.tf
 ┣ 📜main-vars.tf
 ┣ 📜terraform.tfvars
 ┣ 📜outputs.tf
 ┗ 📜main.tf
```

### 1\. Generate an SSH key

This is optional, but if we want to access the instance locally, we need a public and private SSH key. Run:
```sh
ssh keygen
```
I generate my private and public keys inside the directory with the name `id_rsa`.

### 2\. Declare cloud provider in `terraform.tf`

We declare `hashicorp/oci` as the provider for this Terraform deployment, connecting to OCI.

```tf
# terraform.tf
terraform {
  required_providers {
    oci = {
      source  = "hashicorp/oci"
      version = ">= 5.0.0"
    }
  }
}
```

### 3\. Declare profile in `provider.tf`

This step is required if you have multiple OCI profiles in your `~/.oci/config` file to declare which profile you want to use. By default, the profile is DEFAULT.

```tf
# provider.tf
provider "oci" {
  config_file_profile = "DEFAULT"
}
```

### 4\. Declare variables in `main-vars.tf`

For simplicity, we also initialize most of the variables here. It is aesthetically preferred for you to initialize them in another file (Step 5).

```tf
# main-vars.tf
############################################
# Compartments
############################################

variable "compartment_id" {
  description = "The OCID of the parent compartment where the resources will be created."
  type        = string
}

variable "compartment_name" {
  description = "Compartment Name"
  type        = string
  default     = "test-compartment"
}

variable "compartment_description" {
  description = "Compartment Description"
  type        = string
  default     = "test-compartment description"
}

############################################
# VCN
############################################

variable "vcn1" {
  description = "The details of VCN1."
  default = {
    cidr_blocks : ["10.23.0.0/20"]
    display_name : "vcn01"
  }
}

############################################
# Public Subnet, Route Table, and Internet Gateway
############################################

variable "subnetA_pub" {
  description = "The details of the subnet"
  default = {
    cidr_block : "10.23.11.0/24"
    display_name : "IC_pub_snet-A"
    is_public : true
    route_table : {
      display_name = "routeTable-Apub"
      description  = "routeTable-Apub"
    }
  }
}

variable "internet_gateway_A" {
  description = "The details of the internet gateway"
  default = {
    display_name : "IC_IG-A"
    ig_destination = "0.0.0.0/0"
  }
}

############################################
# Compute Instance
############################################

variable "ic_pub_vm_A" {
  description = "The details of the compute instance"
  default = {
    display_name : "IC_pub_vm-A"
    assign_public_ip : true
    availability_domain : "KGRc:US-ASHBURN-AD-1"
    image_ocid : "ocid1.image.oc1.iad.aaaaaaaacnjdag7ngxnzim3ogkgfywf4aoclwz4gkcaqdr773hukt5o2ahma"
    shape : {
      name          = "VM.Standard.E4.Flex"
      ocpus         = 1
      memory_in_gbs = 8
    }
    ssh_authorized_keys = ["ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCuzY9AR7LiJN8EhHeG3qP9gWuf7IxUl+xDaf1gD/zvZjid4Uxa8fRjWkAkeRQGa1ZNBLjw7EH+zWpjqOlCg14eZqTUnNtmOzIfK/LmcSFNKmD2rGNryY8DQBH5cY94bZVasOA+lhxnaNOzJ0sDGrqeCrpqTWqGWZ2NZ/nxXSXTdescHYcz/lmEijRLGnxtI/ByWKufowPUQm9gA0x+DRqJk9mvT7i1ZHi9djbeVPJPZthn14Ppi5cjLIXtCrxTQUcALaCPkzcgAKen9KHlmNRfoW+hx8fRxC0RPZgYCAigqz3hktsnjr+n4pxkF+5e55ZJJYdAKQnaYbS5SVzegC9jNzyzxef8JmZqtrgTBo4dsvNdbw7iIn0/KGgK3xZNTR55L60kS4y4NPbVNhRey8ESjIRc2zoBycssLmVd8cp0a0iLdjXDH3PGLgfC0Ly3Tv7lmGLd27c3U7ndN6ldXxFJ9k7B9k6EibyoaQJM0fLX4eug1tGa6BaB3dwEyZgSUuM= fdurrani@AJTV3VGQF2.local"]
  }
}
```

Replace `ssh_authorized_keys` with your public SSH key that you generated in Step 1.

A few places for more customization:
- `image_ocid` is the OS image of the VM in the instance. I picked Oracle Linux 9.0 which I found amongst other images [here](https://docs.oracle.com/en-us/iaas/images/image/f5d7268a-240d-407d-9649-a525cba56636/)
- `KGRc:US-ASHBURN-AD-1` is availability domain 1 for this tenancy. You can find all availability domains using the CLI: `oci iam availability-domain list`

### 5\. Initialize variables in `terraform.tfvars`

```tf
# terraform.tfvars
compartment_id = "ocid1.compartment.oc1..aaaaaaaakbr7t5yqjirrmwm3ycg5jj4hytparentcompartmenterlfndenwfskn"
```

Replace `compartmend_id` with the parent compartment of this new compartment or use your tenancy OCID found at **Profile Picture** > **Tenancy**.

### 6\. Put any print statements in `outputs.tf`

This step is completely optional but if you want to print any variables, use this file:

```tf
# outputs.tf
output "compartment_id" {
  value = oci_identity_compartment.example_compartment.id
}
```

### 7\. The main file: `main.tf`

We do our actual deployments using this file. You may split this into different files if you want.

```tf
# main.tf
locals {
  cmpt_name_prefix = "A506"
  time_f           = formatdate("HHmmss", timestamp())
}

############################################
# Compartments
############################################
resource "oci_identity_compartment" "example_compartment" {
  # Required
  compartment_id = var.compartment_id
  description    = var.compartment_description
  name           = "${local.cmpt_name_prefix}-${var.compartment_name}-${local.time_f}"
}

############################################
# VCN
############################################

resource "oci_core_vcn" "example_vcn" {
  #Required
  compartment_id = oci_identity_compartment.example_compartment.id
  cidr_blocks    = var.vcn1.cidr_blocks
  #Optional
  display_name = var.vcn1.display_name
}

############################################
# Public Subnet
############################################

resource "oci_core_subnet" "subnetA_pub" {
  #Required
  compartment_id = oci_identity_compartment.example_compartment.id
  vcn_id         = oci_core_vcn.example_vcn.id
  cidr_block     = var.subnetA_pub.cidr_block
  #Optional
  display_name               = var.subnetA_pub.display_name
  prohibit_public_ip_on_vnic = !var.subnetA_pub.is_public
  prohibit_internet_ingress  = !var.subnetA_pub.is_public
}

############################################
# Internet Gateways and NAT Gateways
############################################

resource "oci_core_internet_gateway" "the_internet_gateway" {
  compartment_id = oci_identity_compartment.example_compartment.id
  vcn_id         = oci_core_vcn.example_vcn.id
  display_name   = var.internet_gateway_A.display_name
}


############################################
# Route Tables
############################################

resource "oci_core_default_route_table" "the_route_table" {
  #Required
  compartment_id             = oci_identity_compartment.example_compartment.id
  manage_default_resource_id = oci_core_vcn.example_vcn.default_route_table_id
  # Optional
  display_name = var.subnetA_pub.route_table.display_name
  dynamic "route_rules" {
    for_each = [true]
    content {
      destination       = var.internet_gateway_A.ig_destination
      description       = var.subnetA_pub.route_table.description
      network_entity_id = oci_core_internet_gateway.the_internet_gateway.id
    }
  }
}

# ############################################
# # Compute Instance
# ############################################

resource "oci_core_instance" "ic_pub_vm-A" {
  compartment_id      = oci_identity_compartment.example_compartment.id
  shape               = var.ic_pub_vm_A.shape.name
  availability_domain = var.ic_pub_vm_A.availability_domain
  display_name        = var.ic_pub_vm_A.display_name

  source_details {
    source_id   = var.ic_pub_vm_A.image_ocid
    source_type = "image"
  }

  dynamic "shape_config" {
    for_each = [true]
    content {
      #Optional
      memory_in_gbs = var.ic_pub_vm_A.shape.memory_in_gbs
      ocpus         = var.ic_pub_vm_A.shape.ocpus
    }
  }

  create_vnic_details {
    subnet_id        = oci_core_subnet.subnetA_pub.id
    assign_public_ip = var.ic_pub_vm_A.assign_public_ip
  }

  metadata = {
    ssh_authorized_keys = join("\n", [for k in var.ic_pub_vm_A.ssh_authorized_keys : chomp(k)])
  }
}
```

### 8\. Initialize and Apply Terraform plan
```sh
terraform init
terraform fmt .
terraform validate .
terraform plan --out="plan.out"
terraform apply "plan.out"
```

If successful, you should see a success message like the following:


![Success message in Terminal after deploying Terraform plan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rvuzp5csgd8y8gzemui7.png)


### 9\. Verify

The most straightforward to ensure correct deployment is SSH-ing into your new VM instance. 

Searching **Instances** on the Oracle Console search bar (in the new compartment we created), we find the new Instance and its public IP address:

![New instance on OCI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aa7npi5uzy3m45fwf6v8.png)

Let us access the VM. In the `tf-test-1/` directory,

```sh
ssh -i id_rsa opc@182.181.138.162
```
where
- `182.181.138.162` is the public IP of the instance

![SSH-ing into the new instance](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9n2jsc11b8td1e808si3.png)

We have successfully accessed the new VM instance.

Searching **Network visualizer** on the OCI Console, we see the deployed VCN and Internet Gateway:

![Network visualizer on OCI](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmzz0dertm5i1gcsvnhv.png)

## Destroying a Terraform deployment
To destroy everything deployed in the current plan, simply
```sh
terraform destroy
```

---

***Safe harbor statement***
The information provided on this channel/article/story is solely intended for informational purposes and cannot be used as a part of any contractual agreement. The content does not guarantee the delivery of any material, code, or functionality, and should not be the sole basis for making purchasing decisions. The postings on this site are my own and do not necessarily reflect the views or work of Oracle or Mythics, LLC.

*This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/).*