Oci (Cloud)

From campisano.org
Jump to navigation Jump to search

OCI - Oracle Cloud Infrastructure

There is a really free and useful free tier offer at Oracle Cloud Infrastructure (OCI), where we can obtain, for instance, up two Virtual Private Server (VPS) with AMD/Intel CPU and 1 GB of memory, and also up to 4 ARM64 CPU with 24 GB of memory (there are tricky limits for the storage disk, 200 GB free in total, but each instance requires 50GB so you can obtain up to 4 VPS with 50 GB of disk at max, not bad! more details here) (and a free Oracle database instance and others stuffs), completely free and forever (under certain limits like bandwidth consumption etc.), so lets start with a cuple of free VPS using Terraform.

Configure a OCI account

  • Create the Oracle config file (~/.oci/config) configured to have access to the OCI API services using APIKeyAuth authentication. This will be used by Terraform to create and manage resources e.g. the VPS creation. Follow those steps:
go to https://cloud.oracle.com/identity/users
open you user api-key section
click on add apikey
download private key to ~/.oci/my-api-key.pem
click on create
save the config file snippet content in ~/.oci/config sobstituting the key_file value with ~/.oci/my-api-key.pem
  • Configure the tenancy_ocid variable in the var.json file with the value saved in the ~/.oci/config file.
  • Choose a O.S. image to use in your VPSs. A list is available here. In this example we will use Canonical-Ubuntu-20.04-Minimal-2021.05.17-0.
x86 shapes and Arm-based shapes are supported with this image. For Arm-based shapes, use the Ubuntu image, not Minimal Ubuntu.

Create a custom ssh keypair

  • Define a SSH Key Pair to have access to the VPS. To create a new keypair, do the following:
ssh-keygen -q -t rsa -b 2048 -N '' -f ~/.ssh/oci-keypair
chmod 400 ~/.ssh/oci-keypair

Install Terraform

Terraform allows to manage Infrastructure as Code, so that you can programmatically define (and versioning) your infrastructure using code and setup virtual servers and other resources in the Cloud in few minutes.

To install, follow Terraform_(Application) instructions or see the official doc.

Setup free virtual machine using Terraform

Note: the source code is available at GitLab - terraform_oci_module.

  • configure Terraform
cat > versions.tf << 'EOF'
terraform {
  required_version = ">= 0.13"

  required_providers {
    oci = ">= 4.0"
  }
}
EOF
  • define a oci provider
cat > provider.tf << 'EOF'
provider "oci" {
  auth                = var.oci_provider.auth
  config_file_profile = var.oci_provider.config_file_profile
}
EOF
  • create a module to optionally manage multiple virtual networks
mkdir -p modules/oci/vcn
cat > modules/oci/vcn/input.tf << 'EOF'
variable "name"           { type = string }
variable "compartment_id" { type = string }
variable "cidr_block"     { type = string }
EOF
cat > modules/oci/vcn/main.tf << 'EOF'
resource "oci_core_vcn" "vcn" {
  compartment_id = var.compartment_id
  display_name   = var.name
  cidr_block     = var.cidr_block
}

resource "oci_core_internet_gateway" "internet_gateway" {
  compartment_id = var.compartment_id
  display_name   = "${var.name}-internet-gateway"
  vcn_id         = oci_core_vcn.vcn.id
}

resource "oci_core_default_route_table" "default_route_table" {
  display_name               = "${var.name}-default-route-table"
  manage_default_resource_id = oci_core_vcn.vcn.default_route_table_id

  route_rules {
    destination       = "0.0.0.0/0"
    destination_type  = "CIDR_BLOCK"
    network_entity_id = oci_core_internet_gateway.internet_gateway.id
  }
}
EOF
cat > modules/oci/vcn/output.tf << 'EOF'
output "vcn" {
  value = oci_core_vcn.vcn
}
EOF
  • create a module to optionally manage multiple subnets
mkdir -p modules/oci/subnet
cat > modules/oci/subnet/input.tf << 'EOF'
variable "name"           { type = string }
variable "compartment_id" { type = string }
variable "vcn"            { type = any }
variable "ad_name"        { type = string }
variable "cidr_block"     { type = string }
EOF
cat > modules/oci/subnet/main.tf << 'EOF'
resource "oci_core_subnet" "subnet" {
  compartment_id      = var.compartment_id
  display_name        = var.name
  availability_domain = var.ad_name
  cidr_block          = var.cidr_block
  security_list_ids   = [var.vcn.default_security_list_id]
  vcn_id              = var.vcn.id
  route_table_id      = var.vcn.default_route_table_id
  dhcp_options_id     = var.vcn.default_dhcp_options_id
}
EOF
cat > modules/oci/subnet/output.tf << 'EOF'
output "subnet" {
  value = oci_core_subnet.subnet
}
EOF
  • create a module to optionally manage multiple instances
mkdir -p modules/oci/instance
cat > modules/oci/instance/input.tf << 'EOF'
variable "name"             { type = string }
variable "compartment_id"   { type = string }
variable "subnet_id"        { type = string }
variable "ad_name"          { type = string }
variable "keypair_path"     { type = string }
variable "instance_shape"   { type = string }
variable "image_ocid"       { type = string }
variable "boot_disk_size"   { type = string }
variable "static_ip"        {
  type = bool
  default = false
}
variable "init_script_path" {
  type = string
  default = null
}
EOF
cat > modules/oci/instance/main.tf << 'EOF'
data "template_file" "init_script" {
  count = var.init_script_path != null ? 1 : 0

  template = file(var.init_script_path)
}

resource "oci_core_instance" "instance" {
  compartment_id      = var.compartment_id
  display_name        = var.name
  availability_domain = var.ad_name
  shape               = var.instance_shape

  source_details {
    source_type             = "image"
    source_id               = var.image_ocid
    boot_volume_size_in_gbs = var.boot_disk_size
  }

  create_vnic_details {
    display_name     = "${var.name}-primary-vnic"
    subnet_id        = var.subnet_id
    assign_public_ip = var.static_ip
  }

  metadata = {
    ssh_authorized_keys = file(var.keypair_path)
    user_data           = var.init_script_path == null ? null : base64encode(data.template_file.init_script[0].rendered)
  }

  preserve_boot_volume = false
}
EOF
cat > modules/oci/instance/output.tf << 'EOF'
output "static_ip" {
  value = oci_core_instance.instance.public_ip
}
EOF
  • configure the modules using external variables
cat > input.tf << 'EOF'
variable "oci_provider"        { type = map(string) }
variable "oci_vcn_module"      { type = map(any) }
variable "oci_subnet_module"   { type = map(any) }
variable "oci_instance_module" { type = map(any) }
EOF
cat > main.tf << 'EOF'
data "oci_identity_availability_domain" "ad" {
  compartment_id = var.oci_provider.tenancy_ocid
  ad_number      = 1
}

resource "oci_identity_compartment" "compartment" {
  compartment_id = var.oci_provider.tenancy_ocid
  name           = "tf-compartment"
  description    = "compartment created by terraform"
  enable_delete  = true
}

module "oci_vcn" {
  source = "./modules/oci/vcn"

  for_each = var.oci_vcn_module

  name              = each.key
  compartment_id    = oci_identity_compartment.compartment.compartment_id
  cidr_block        = each.value.cidr_block
}

module "oci_subnet" {
  source = "./modules/oci/subnet"

  for_each = var.oci_subnet_module

  name              = each.key
  compartment_id    = oci_identity_compartment.compartment.compartment_id
  vcn               = module.oci_vcn[each.value.vcn_name].vcn
  ad_name           = data.oci_identity_availability_domain.ad.name
  cidr_block        = each.value.cidr_block
}

module "oci_instance" {
  source = "./modules/oci/instance"

  for_each = var.oci_instance_module

  name             = each.key
  compartment_id   = oci_identity_compartment.compartment.compartment_id
  subnet_id        = module.oci_subnet[each.value.subnet_name].subnet.id
  ad_name          = data.oci_identity_availability_domain.ad.name
  keypair_path     = each.value.keypair_path
  instance_shape   = each.value.instance_shape
  image_ocid       = each.value.image_ocid
  boot_disk_size   = each.value.boot_disk_size
  static_ip        = lookup(each.value, "static_ip", false)
  init_script_path = lookup(each.value, "init_script_path", null)
}
EOF
cat > output.tf << 'EOF'
output "oci_identity_compartment" {
  value = oci_identity_compartment.compartment.compartment_id
}

output "oci_instances" {
  value = {for key, val in module.oci_instance : key => val.static_ip}
}
EOF
  • configure external variables
cat > vars.json << 'EOF'
{
    "oci_provider": {
        "auth": "APIKey",
        "config_file_profile": "DEFAULT",
        "tenancy_ocid": "ocid1.tenancy.oc1..aaaaaaaau53ror64zva7gnsrnahfq23ksnngiyglsf4dutzltbb7dag7ugua"
    },
    "oci_vcn_module": {
        "tf-vcn1": {
            "cidr_block": "10.1.0.0/16"
        }
    },
    "oci_subnet_module": {
        "tf-vcn1-subnet1": {
            "vcn_name": "tf-vcn1",
            "cidr_block": "10.1.20.0/24"
        },
        "tf-vcn1-subnet2": {
            "vcn_name": "tf-vcn1",
            "cidr_block": "10.1.21.0/24"
        }
    },
    "oci_instance_module": {
        "tf-instance1": {
            "subnet_name": "tf-vcn1-subnet1",
            "keypair_path": "~/.ssh/oci-keypair.pub",
            "instance_shape": "VM.Standard.E2.1.Micro",
            "image_ocid": "ocid1.image.oc1.eu-amsterdam-1.aaaaaaaae37x4oll5jixkbmkc63pk25ggvjh3h4iug7trp35agtexcpatw6q",
            "boot_disk_size": 100,
            "static_ip": true,
            "init_script_path": "init_script.sh"
        },
        "tf-instance2": {
            "subnet_name": "tf-vcn1-subnet2",
            "keypair_path": "~/.ssh/oci-keypair.pub",
            "instance_shape": "VM.Standard.E2.1.Micro",
            "image_ocid": "ocid1.image.oc1.eu-amsterdam-1.aaaaaaaae37x4oll5jixkbmkc63pk25ggvjh3h4iug7trp35agtexcpatw6q",
            "boot_disk_size": 100,
            "static_ip": true,
            "init_script_path": "init_script.sh"
        }
    }
}
EOF
  • (optionally) configure init_script.sh

The script can be used to customize the O.S. image. You may use it as example or skip it.

cat > init_script.sh << 'EOF'
#!/bin/bash

export DEBIAN_FRONTEND=noninteractive

apt-get -y update
apt-get -y dist-upgrade
apt-get -y clean
sync

echo "custom init script completed" > /var/log/init_script.log
EOF
  • execute creation
terraform init
terraform apply -input=false -var-file=vars.json
  • destroy all resources
terraform destroy -input=false -var-file=vars.json
  • Optionally, use a simple Makefile to setup the previous custom commands:
cat > Makefile << 'EOF'
export TF_PLUGIN_CACHE_DIR := ${HOME}/.terraform.d/plugin-cache

.PHONY: clean
clean:
	@mkdir -p ${TF_PLUGIN_CACHE_DIR} || true
	@rm -rf .terraform

.PHONY: init
init: clean
	terraform init

.PHONY: apply
apply:
	terraform apply -input=false -var-file=vars.json

.PHONY: destroy
destroy:
	terraform destroy -input=false -var-file=vars.json
EOF

References