Getting started with Oracle Cloud Infrastructure

2021-09-05 - Oracle Cloud Infrastructure's always free tier is very generous
Tag: terraform

Introduction

Oracle Cloud Infrastructure provides quite a generous always free tier for you to use and test their cloud… or host some light services. But getting started was a little difficult with many pieces missing or incomplete in the examples, especially how to configure ipv6 on your instances.

The documentation is very good and exhaustive but information was scattered : the following should help you get started right after you create your oracle cloud infrastructure’s account.

Create your API access

In order to terraform your infrastructure, you are going to need to generate an api access which is composed of a key and several other things :

Terraform

Provider configuration

Here is the relevant snippet from my providers.tf file :

variable "oracle_tenancy_ocid" {}
variable "oracle_user_ocid" {}
variable "oracle_fingerprint" {}
provider "oci" {
  tenancy_ocid     = var.oracle_tenancy_ocid
  user_ocid        = var.oracle_user_ocid
  fingerprint      = var.oracle_fingerprint
  private_key_path = "../tf-common/oracle_key.pem"
  region           = "eu-amsterdam-1"
}
variable "oracle_amd64_instances_names" {}

This goes along with a terraform.tfvars file that you should fill with the api access information you saved up earlier :

oracle_tenancy_ocid = "XXXXX"
oracle_user_ocid    = "YYYYY"
oracle_fingerprint  = "ZZZZZ"

oracle_amd64_instances_names = ["dalinar", "kaladin"]

The last bit is how I name the two free instances I want to create, pick anything you like.

Networking

Here is how to bootstrap a vcn and the associated objects for direct internet access. For simplicity I will leave the access lists opened, firewall rules really are a pain to write with terraform… I plan to keep on using iptables or shorewall on the hosts for now.

resource "oci_core_vcn" "adyxax" {
  compartment_id = var.oracle_tenancy_ocid
  cidr_blocks    = ["10.0.0.0/16"]
  display_name   = "adyxax"
  dns_label      = "adyxax"
  is_ipv6enabled = true
}
resource "oci_core_internet_gateway" "gw" {
  compartment_id = var.oracle_tenancy_ocid
  vcn_id         = oci_core_vcn.adyxax.id
  enabled        = true
  display_name   = "gw"
}
resource "oci_core_route_table" "default-via-gw" {
  compartment_id = var.oracle_tenancy_ocid
  vcn_id         = oci_core_vcn.adyxax.id
  display_name   = "default-via-gw"

  route_rules {
    destination       = "0.0.0.0/0"
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_internet_gateway.gw.id
  }
  route_rules {
    destination       = "::/0"
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_internet_gateway.gw.id
  }
}
# protocol - Specify either all or an IPv4 protocol number : ICMP ("1"), TCP ("6"), UDP ("17"), and ICMPv6 ("58").ยท
resource "oci_core_security_list" "allow-all" {
  compartment_id = var.oracle_tenancy_ocid
  vcn_id         = oci_core_vcn.adyxax.id
  display_name   = "allow-all"

  egress_security_rules {
    protocol    = "all"
    destination = "0.0.0.0/0"
  }

  ingress_security_rules {
    protocol = "all"
    source   = "0.0.0.0/0"
  }

  egress_security_rules {
    protocol    = "all"
    destination = "::/0"
  }

  ingress_security_rules {
    protocol = "all"
    source   = "::/0"
  }
}
resource "oci_core_subnet" "adyxax-production" {
  cidr_block     = cidrsubnet(oci_core_vcn.adyxax.cidr_blocks[0], 8, 0)
  compartment_id = var.oracle_tenancy_ocid
  vcn_id         = oci_core_vcn.adyxax.id

  display_name      = "production"
  dns_label         = "production"
  ipv6cidr_block    = cidrsubnet(oci_core_vcn.adyxax.ipv6cidr_blocks[0], 8, 0)
  security_list_ids = [oci_core_security_list.allow-all.id]
  route_table_id    = oci_core_route_table.default-via-gw.id
}

Instances

Here is how to create the two always free tier instances, each in a different fault domain. The tricky part was to understand how ipv6 addresses are like second class citizens on oracle cloud :

data "oci_identity_availability_domains" "ads" {
  compartment_id = var.oracle_tenancy_ocid
}
data "oci_identity_fault_domains" "fd" {
  compartment_id      = var.oracle_tenancy_ocid
  availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
}
# taken from https://docs.oracle.com/en-us/iaas/images/all/?search=Oracle-Linux-8.4
data "oci_core_image" "ol8" {
  image_id = "ocid1.image.oc1.eu-amsterdam-1.aaaaaaaaj46eslsa6ivgneyneypomtvzb6dmg22gtewy6opwiniuwgsdv7uq"
}

resource "oci_core_instance" "amd64-vms" {
  count                = length(var.oracle_amd64_instances_names)
  compartment_id       = var.oracle_tenancy_ocid
  availability_domain  = data.oci_identity_availability_domains.ads.availability_domains[0].name
  fault_domain         = data.oci_identity_fault_domains.fd.fault_domains[count.index % length(data.oci_identity_fault_domains.fd.fault_domains)].name
  display_name         = var.oracle_amd64_instances_names[count.index % length(var.oracle_amd64_instances_names)]
  shape                = "VM.Standard.E2.1.Micro"
  preserve_boot_volume = true
  create_vnic_details {
    subnet_id      = oci_core_subnet.adyxax-production.id
    hostname_label = var.oracle_amd64_instances_names[count.index]
    display_name   = var.oracle_amd64_instances_names[count.index]
  }
  source_details {
    boot_volume_size_in_gbs = 50
    source_type             = "image"
    source_id               = data.oci_core_image.ol8.id
  }
  metadata = {
    "ssh_authorized_keys" : "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILOJV391WFRYgCVA2plFB8W8sF9LfbzXZOrxqaOrrwco"
  }
}
data "oci_core_vnic_attachments" "amd64-vms-vnics" {
  count          = length(var.oracle_amd64_instances_names)
  compartment_id = var.oracle_tenancy_ocid
  instance_id    = oci_core_instance.amd64-vms[count.index].id
}
resource "oci_core_ipv6" "amd64-vms-ipv6s" {
  count        = length(var.oracle_amd64_instances_names)
  vnic_id      = data.oci_core_vnic_attachments.amd64-vms-vnics[count.index].vnic_attachments[0].vnic_id
  display_name = var.oracle_amd64_instances_names[count.index]
}

Bonus : Provisionning cloudflare’s dns

If like me you are managing your dns with cloudflare, here is how to provision the relevant records :

resource "cloudflare_record" "adyxax-org-oracle-amd64-vms-ipv4" {
  count   = length(var.oracle_amd64_instances_names)
  zone_id = lookup(data.cloudflare_zones.adyxax-org.zones[0], "id")
  name    = var.oracle_amd64_instances_names[count.index]
  value   = oci_core_instance.amd64-vms[count.index].public_ip
  type    = "A"
  proxied = false
}
resource "cloudflare_record" "adyxax-org-oracle-amd64-vms-ipv6" {
  count   = length(var.oracle_amd64_instances_names)
  zone_id = lookup(data.cloudflare_zones.adyxax-org.zones[0], "id")
  name    = var.oracle_amd64_instances_names[count.index]
  value   = oci_core_ipv6.amd64-vms-ipv6s[count.index].ip_address
  type    = "AAAA"
  proxied = false
}

Conclusion

Putting all of this together was an interesting experience, and I am satisfied that it works well. In the future I plan to add my own oci image based on alpine linux which is not available natively. I tried oracle linux and it is fine, but consumes way too much ram for my taste. For now I installed alpine linux using the instance’s cloud console and my procedure for that.