Oci (Cloud)
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 an OCI free 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.
- Note about ARM Ubuntu minimal images: from this link OracleProvided_Images
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
- https://docs.oracle.com/en-us/iaas/developer-tutorials/tutorials/tf-compute/01-summary.htm
- https://registry.terraform.io/providers/hashicorp/oci/4.24.0/docs
- https://dev.to/phocks/how-to-get-2x-oracle-cloud-servers-free-forever-4o22
- https://github.com/terraform-providers/terraform-provider-oci/blob/master/examples/always_free/main.tf
- https://docs.oracle.com/en-us/iaas/Content/API/SDKDocs/terraformproviderconfiguration.htm#terraformproviderconfiguration_topic-SDK_and_CLI_Config_File
- https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm#SDK_and_CLI_Configuration_File