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
Database
Oracle offer a free db instance
an example java connection:
docker run -it --rm openjdk:11 /bin/bash
wget https://download.oracle.com/otn-pub/otn_software/jdbc/215/ojdbc11.jar
cat > JDBCExample.java << 'EOF'
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class JDBCExample {
public static void main(String[] args) {
try (Connection conn = DriverManager.getConnection("jdbc:oracle:thin:@(description= (retry_count=1)(retry_delay=3)(address=(protocol=tcps)(port=1521)(host=adb.eu-amsterdam-1.oraclecloud.com))(connect_data=(service_name=gef737c78aa7665_test_medium.adb.oraclecloud.com))(security=(ssl_server_dn_match=yes)(ssl_server_cert_dn=\"CN=adb.eu-amsterdam-1.oraclecloud.com, OU=Oracle ADB AMSTERDAM, O=Oracle Corporation, L=Redwood City, ST=California, C=US\")))", "admin", "<YOUR_PASSWORD>")) {
if (conn != null) {
System.out.println("Connected to the database!");
} else {
System.out.println("Failed to make connection!");
}
} catch (SQLException e) {
System.err.format("SQL State: %s\n%s", e.getSQLState(), e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
EOF
javac JDBCExample.java
java -Djava.security.egd=file:/dev/./urandom -cp "$(pwd)/ojdbc11.jar:$(pwd)" JDBCExample
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