Note:
- This tutorial requires access to Oracle Cloud. To sign up for a free account, see Get started with Oracle Cloud Infrastructure Free Tier.
- It uses example values for Oracle Cloud Infrastructure credentials, tenancy, and compartments. When completing your lab, substitute these values with ones specific to your cloud environment.
Attach a Reserved IP to an Oracle Cloud Infrastructure Compute Instance using Terraform and cloud-init Script
Introduction
In the world of infrastructure as code (IaC), automating the provisioning and configuration of compute instances is essential for efficient and scalable cloud deployments. When it comes to attaching Reserved IP addresses to Oracle Cloud Infrastructure (OCI) Compute Instances, Terraform and cloud-init provide a powerful combination.
In OCI Compute Terraform module, there is no option to Attach a Reserved IP to a Compute Instance while the instance gets created. We can only attach Reserved IP after creation of the Instance. In this blog post, we will explore how to leverage Terraform and cloud-init script to attach a reserved IP to an OCI Compute instance seamlessly.
Objective
Attach a reserved IP to a OCI Compute instance seamlessly using Terraform and cloud-init script.
What is a Reserved IP?
A Reserved IP address is a static IP assigned to a compute instance within a virtual network. It ensures consistent connectivity for applications that require a fixed IP and simplifies network administration. Reserved IPs are commonly used in scenarios such as load balancing, firewall rules, and secure network communication.
Prerequisites
First, you need access to an OCI environment. If you don’t have one, you can easily create your Always Free instance, which comes with lots of free capacity and US$300 in trial credits.
When you have an environment, ensure that your user has been assigned to a group with permissions to manage the instance-family resources and storage-family. A security best practice has you create a user, instead of using a Console user that already has permissions assigned to it. If you need help, check out the docs to create a group and grant proper permissions to users.
- OCI Account
- Terraform Access. See this link for more details.
- Public Subnet, where the instance will be launched. This has to be attached to an Internet Gateway.
- Private Subnet, where the secondary VNIC gets created and assigns the Reserved IP. This has to be attached to a Service Gateway.
- Dynamic Group wherein the Resources in the group require the permissions to manage Network Family.
- Policies must be configured for Instance Principal Authentication.
Task 1: Set Up a Terraform Script
Here, we will add a cloud-init script as metadata to an OCI Terraform Create Compute Instance module. Also, we will pass Reserved IP OCID and Private Subnet OCID to the Compute VM as freeform tags.
resource "oci_core_public_ip" "pubip" {
# In case of Multiple Instances has to be created, Provide the count in the vars file.
# This block creates a Reserved Public IP from Oracle IP Pool
count = var.instance_count
compartment_id = var.compartment_id
display_name = var.res_ip_display_name
lifetime = "RESERVED"
private_ip_id = ""
}
resource "oci_core_instance" "pubiptest" {
count = var.instance_count
compartment_id = var.compartment_id
availability_domain = data.oci_identity_availability_domains.ads.availability_domains[0].name
display_name = var.display_name
shape = var.shape
# We will pass Reserved Public IP OCID and Private Subnet OCID as a freeform tags to the instance
freeform_tags = {
"publicIP" = resource.oci_core_public_ip.pubip[count.index].id
"SubnetId" = var.private_subnet_id
}
create_vnic_details {
display_name = var.vnic_display_name
assign_public_ip = true
subnet_id = var.public_subnet_id
}
source_details {
source_type = var.source_type
source_id = var.source_id
}
metadata = {
ssh_authorized_keys = var.pub_key
user_data = "${base64encode(file("./cloud-init.sh"))}"
}
}
The above script will create a Reserved IP (considering the count variable value) and will spin up a Compute Instance with Ephemeral IP, which gets attached to the Primary VNIC.
In the Compute Metadata, we will pass the cloud-init script, which will assign the Reserved IP by replacing the existing Ephemeral IP.
Task 2: Understand the cloud-init
Script
Task 2.1: Install OCI CLI and other required tools
Use the following command to install OCI CLI and jq (Parsing JSON).
`sudo yum install -y python36-oci-cli jq`
Task 2.2: Retrieve Instance Metadata
-
Use the instance metadata service, which provides information about a running instance from Networking, Volumes, and so on. This retrieves Instance OCID, Compartment ID, subnetID (Private Subnet OCID passed as a freeform tag) and publicIP (Reserved IPOCID passed as a freeform tag) using the instance metadata service which includes freeform tags values as well.
metadata=$(curl -H 'Authorization: Bearer Oracle' http://169.254.169.254/opc/v2/instance) instanceid=$(echo $metadata | jq -r '.id') compartmentid=$(echo $metadata | jq -r '.compartmentId') subnetid=$(echo $metadata | jq -r '.freeformTags.SubnetId') publicIp=$(echo $metadata | jq -r '.freeformTags.publicIP')
-
Retrieve the Primary VNIC OCID, Private IP ID (OCID of Private IP assigned to Primary VNIC), Ephemeral Public IP and Ephemeral Public IP OCID using
oci-cli
primaryvnicid=$(curl -H 'Authorization: Bearer Oracle' http://169.254.169.254/opc/v1/vnics/ | jq -r '.[].vnicId') privateIpId=$(oci network private-ip list --vnic-id $primaryvnicid --auth instance_principal | jq -r '.data[].id') ephpublicIp=$(oci network vnic get --vnic-id $primaryvnicid --auth instance_principal | jq -r '.data."public-ip"') ephpublicIpId=$( oci network public-ip get --auth instance_principal --public-ip-address $ephpublicIp | jq -r '.data.id')
Task 2.3: Attach Secondary VNIC
Attach the Secondary VNIC to the Compute VM which will use the Private Subnet by running the following command.
oci compute instance attach-vnic --instance-id $instanceid --subnet-id $subnetid --auth instance_principal --wait
Task 2.4: Configure Routes
-
Once the Secondary VNIC is created and attached to the VM, we will configure the Secondary VNIC and add them to the routes. To configure the routes, we will use the Script provided by Oracle.
wget https://docs.oracle.com/en-us/iaas/Content/Resources/Assets/secondary_vnic_all_configure.sh sudo chmod u+x ./secondary_vnic_all_configure.sh sudo ./secondary_vnic_all_configure.sh -c
-
Once the configuration is done, you will see the new route added to the VM routes. You can check the same using the
sudo route -n
command.
Task 2.5: Make Secondary VNIC route as Default route
-
Get the Gateway IP for the Secondary VNIC using IDMS. This will be added to the Instance Route Table.
gatewayIP=$(curl http://169.254.169.254/opc/v1/vnics/ | jq -c '.[] | ( select(.vnicId=="'$secondaryvnicid'" ))' | jq -r '.virtualRouterIp')
-
Get the Default Network Interface using the below command.
iname=$(sudo ./secondary_vnic_all_configure.sh | awk '{print $8}' | awk 'END{print}')
-
Execute the below command to add the default route to be Secondary VNIC.
sudo route add default gw $gatewayIP dev $iname
-
Confirm again using the
sudo route -n
command.
Task 2.6: Delete Ephemeral Public IP
-
Delete the Ephemeral Public IP which is attached to the VM. Since, we had made the Secondary VNIC as the default route and the subnet which has secondary VNIC exists, it has a service gateway attached to it, so that it can talk to other OCI services (VCN in this case).
delpubIp=$(oci network public-ip delete --force --auth instance_principal --public-ip-id $ephpublicIpId --wait-for-state TERMINATED)
-
Use the below command to check whether Ephemeral IP has been successfully deleted.
checkIp=$(oci network vnic get --vnic-id $primaryvnicid --auth instance_principal | jq -r '.data."public-ip"')
Task 2.7: Attach Reserved IP
We have the Public IP OCID from freeform tags and we have deleted the Ephermeral IP, so we can attach the Reserved IP to the Primary VNIC. Below command attaches Reserved IP to the Primary VNIC of the instance.
assign=$(oci network public-ip update --force --auth instance_principal --public-ip-id $publicIp --private-ip-id $privateIpId --wait-for-state ASSIGNED)
Task 2.8: Detach Secondary VNIC
-
We have attached Reserved Public to the Primary VNIC and now we are good to detach the Secondary VNIC. Use the below command, to detach the secondary VNIC and to update the route rules accordingly.
detach=$(oci compute instance detach-vnic --compartment-id $compartmentid --vnic-id $secondaryvnicid --force --auth instance_principal) sudo ./secondary_vnic_all_configure.sh -d
-
Following script shows all steps together. This script has retry up to 3 times in case of failure and we can setup notification on failure using the OCI Events service.
#!/bin/bash # coding: utf-8 sudo yum install -y python36-oci-cli function getdetails() { # Fetch data using instance principal authentication metadata=$(curl -H 'Authorization: Bearer Oracle' http://169.254.169.254/opc/v2/instance) instanceid=$(echo $metadata | jq -r '.id') compartmentid=$(echo $metadata | jq -r '.compartmentId') subnetid=$(echo $metadata | jq -r '.freeformTags.SubnetId') publicIp=$(echo $metadata | jq -r '.freeformTags.publicIP') primaryvnicid=$(curl -H 'Authorization: Bearer Oracle' http://169.254.169.254/opc/v1/vnics/ | jq -r '.[].vnicId') privateIpId=$(oci network private-ip list --vnic-id $primaryvnicid --auth instance_principal | jq -r '.data[].id') ephpublicIp=$(oci network vnic get --vnic-id $primaryvnicid --auth instance_principal | jq -r '.data."public-ip"') ephpublicIpId=$( oci network public-ip get --auth instance_principal --public-ip-address $ephpublicIp | jq -r '.data.id') if [[ -z "$subnetid" || "$subnetid" == "" || ${#subnetid} == 0 || -z "$publicIp" || ${#publicIp} == 0 || -z "$instanceid" || -z "$compartmentid" || -z "$publicIpData" || -z "$privateIpId" || -z "$primaryvnicid" || -z "$ephpublicIp" || -z "$ephpublicIpId" ]]; then echo "Missing some details, retrying..." return 1 fi return 0 } # Retry the `getdetails` function thrice attachVNIC() { attachvnic=$(oci compute instance attach-vnic --instance-id $instanceid --subnet-id $subnetid --auth instance_principal --wait) } for i in {1..3}; do getdetails if [[ $? -eq 0 ]]; then attachVNIC if [[ -n "$attachvnic" ]]; then secondaryvnicid=$(echo $attachvnic | jq -r '.data.id') echo $secondaryvnicid check2VNICs=$(oci compute instance list-vnics --instance-id $instanceid --auth instance_principal | jq -r '.data[].id') echo $check2VNICs gatewayIP=$(curl http://169.254.169.254/opc/v1/vnics/ | jq -c '.[] | ( select(.vnicId=="'$secondaryvnicid'" ))' | jq -r '.virtualRouterIp') if [[ (-n "$gatewayIP") ]]; then echo $gatewayIP wget https://docs.oracle.com/en-us/iaas/Content/Resources/Assets/secondary_vnic_all_configure.sh sudo chmod u+x ./secondary_vnic_all_configure.sh sudo ./secondary_vnic_all_configure.sh -c iname=$(sudo ./secondary_vnic_all_configure.sh | awk '{print $8}' | awk 'END{print}') echo $iname sudo route add default gw $gatewayIP dev $iname route oci os ns get --auth instance_principal delpubIp=$(oci network public-ip delete --force --auth instance_principal --public-ip-id $ephpublicIpId --wait-for-state TERMINATED) checkIp=$(oci network vnic get --vnic-id $primaryvnicid --auth instance_principal | jq -r '.data."public-ip"') if [[ -z "$checkIp" || "$checkIp" == "null" ]]; then echo "Assigning Reserved IP" assign=$(oci network public-ip update --force --auth instance_principal --public-ip-id $publicIp --private-ip-id $privateIpId --wait-for-state ASSIGNED) detach=$(oci compute instance detach-vnic --compartment-id $compartmentid --vnic-id $secondaryvnicid --force --auth instance_principal) sudo ./secondary_vnic_all_configure.sh -d checkIp2=$(oci network vnic get --vnic-id $primaryvnicid --auth instance_principal | jq -r '.data."public-ip"') echo "Reserved IP $checkIp2" if [[ -z "$checkIp2" ]]; then oci compute instance update --instance-id $instanceid --freeform-tags '{"errorStatus":"Reserved IP Not Assigned","SubnetId":"'$subnetid'","publicIP":"'$publicIp'"}' --force --auth instance_principal exit; fi else oci compute instance update --instance-id $instanceid --freeform-tags '{"errorStatus":"Ephemeral IP Not Deleted","SubnetId":"'$subnetid'","publicIP":"'$publicIp'"}' --force --auth instance_principal exit; fi break; else oci compute instance update --instance-id $instanceid --freeform-tags '{"errorStatus":"Gateway IP not fetched","SubnetId":"'$subnetid'","publicIP":"'$publicIp'"}' --force --auth instance_principal exit; fi else break; fi fi done if [[ -z "$attachvnic" ]]; then echo "Failed" oci compute instance update --instance-id $instanceid --freeform-tags '{"errorStatus":"Secondary VNIC not attached","SubnetId":"'$subnetid'","publicIP":"'$publicIp'"}' --force --auth instance_principal exit fi checkVNICs2=$(oci compute instance list-vnics --instance-id $instanceid --auth instance_principal | jq -r '.data | length') if [[ "$checkVNICs2" > 1 ]]; then echo "Detaching Secondary VNIC Failed" oci compute instance update --instance-id $instanceid --freeform-tags '{"errorStatus":"Detaching Secondary VNIC Failed","SubnetId":"'$subnetid'","publicIP":"'$publicIp'"}' --force --auth instance_principal exit fi oci compute instance update --instance-id $instanceid --freeform-tags '{"SubnetId":"'$subnetid'","publicIP":"'$publicIp'"}' --force --auth instance_principal
Next Steps
This is a sample implementation and make the corresponding changes before using this as a cloud-init script. This has been tested with Oracle Linux 8.
If you choose any other Linux Flavor, the oci-cli
installation procedure will change. For more details, see Installing the CLI.
Related Links
Acknowledgments
Author - Rithesh Subramanian (OCI Cloud Architect)
More Learning Resources
Explore other labs on docs.oracle.com/learn or access more free learning content on the Oracle Learning YouTube channel. Additionally, visit education.oracle.com/learning-explorer to become an Oracle Learning Explorer.
For product documentation, visit Oracle Help Center.
Attach a Reserved IP to an Oracle Cloud Infrastructure Compute Instance using Terraform and cloud-init Script
F82850-01
June 2023
Copyright © 2023, Oracle and/or its affiliates.