Introduction to OCI Compute Autoscaling with Instance Pools and Load Balancer Integration
In the dynamic landscape of modern cloud computing, applications often experience fluctuating demand. Imagine an e-commerce platform during a flash sale, a news portal during a breaking event, or a streaming service during prime time. These scenarios demand an infrastructure that can seamlessly scale up to handle peak loads and scale down during periods of low activity to optimize costs. Manually provisioning and de-provisioning compute resources in response to these changes is not only inefficient but also prone to errors and delays.
Oracle Cloud Infrastructure (OCI) provides a robust and integrated solution for this challenge through its Compute autoscaling capabilities, powered by Instance Pools and integrated with Load Balancers. This powerful combination allows organizations to build highly elastic, resilient, and cost-effective applications that automatically adapt to demand changes without manual intervention.
An Instance Pool in OCI acts as a logical grouping of Compute instances that are created from the same instance configuration. It simplifies the management of multiple instances as a single entity. Instead of managing individual virtual machines, you manage the pool, and OCI ensures the desired number of instances are running and healthy.
Autoscaling, when applied to an instance pool, automates the process of adding or removing instances based on predefined metrics and policies. This could be CPU utilization, memory usage, network I/O, or custom metrics. When demand increases, autoscaling adds more instances to the pool; when demand decreases, it removes instances, ensuring optimal resource utilization and cost efficiency.
Finally, a Load Balancer is critical for distributing incoming application traffic across the healthy instances within the instance pool. It acts as a single point of contact for clients, directing requests to available backend servers. When autoscaling adds or removes instances from the pool, the Load Balancer automatically registers and deregisters these instances, ensuring continuous service availability and even traffic distribution.
Together, these OCI services form a highly available, scalable, and self-managing infrastructure. This article will guide you through the detailed, step-by-step process of setting up OCI Compute autoscaling with instance pools and integrating it with an OCI Load Balancer, complete with real-world CLI commands and best practices.
Prerequisites
Before diving into the implementation, ensure you have the following prerequisites in place:
- OCI Account and Permissions: You need an active OCI account with the necessary IAM policies configured to manage compute instances, instance pools, autoscaling configurations, and load balancers. A typical policy set for a user or group would include:
Allow group <your-group-name> to manage instance-family in compartment <your-compartment-name> Allow group <your-group-name> to manage instance-pool-family in compartment <your-compartment-name> Allow group <your-group-name> to manage autoscaling-family in compartment <your-compartment-name> Allow group <your-group-name> to manage lb-family in compartment <your-compartment-name> Allow group <your-group-name> to manage vnics in compartment <your-compartment-name> Allow group <your-group-name> to use subnets in compartment <your-compartment-name> Allow group <your-group-name> to use images in compartment <your-compartment-name> Allow group <your-group-name> to use security-lists in compartment <your-compartment-name> Allow group <your-group-name> to manage network-security-groups in compartment <your-compartment-name> - OCI CLI Installed and Configured: Ensure you have the OCI Command Line Interface (CLI) installed and configured with your OCI tenancy credentials. You can find installation instructions in the OCI documentation.
- Virtual Cloud Network (VCN) and Subnets: You need an existing VCN with at least two subnets:
- A public subnet for the OCI Load Balancer. This subnet should have an internet gateway configured for internet access.
- A private subnet for the Compute instances in the instance pool. While instances can be in a public subnet, placing them in a private subnet behind a public load balancer is a common and recommended security practice. This subnet should have a Service Gateway or NAT Gateway if instances need to access OCI services or the internet for updates.
- SSH Key Pair: An SSH key pair (public and private key) is required to access your compute instances for troubleshooting or initial setup if needed. The public key will be added to the instance configuration.
- Application Image or Configuration: For this demonstration, we'll use a public Oracle Linux image and install a simple Nginx web server using cloud-init. In a real-world scenario, you might use a custom image pre-baked with your application, or a more complex cloud-init script.
Let's start by defining some environment variables for convenience:
# Replace with your actual Compartment OCID
export COMPARTMENT_OCID="ocid1.compartment.oc1..aaaaaaaanexampleocidforcompartment"
# Replace with an existing VCN OCID in your tenancy
export VCN_OCID="ocid1.vcn.oc1..aaaaaaaanexampleocidforvcn"
# Replace with the OCID of a public subnet for the Load Balancer
export LB_SUBNET_OCID="ocid1.subnet.oc1..aaaaaaaanexampleocidforlbsubnet"
# Replace with the OCID of a private subnet for the Compute instances
export INSTANCE_SUBNET_OCID="ocid1.subnet.oc1..aaaaaaaanexampleocidforinstancesubnet"
# Path to your SSH public key
export SSH_PUBLIC_KEY_FILE="~/.ssh/id_rsa.pub"
export SSH_PUBLIC_KEY=$(cat $SSH_PUBLIC_KEY_FILE)
# Desired Availability Domain (e.g., AD-1, AD-2, AD-3 in a region)
# You can list available ADs with: oci iam availability-domain list
export AVAILABILITY_DOMAIN="ocid1.availabilitydomain.oc1..aaaaaaaanexamplead"
# OCI region (e.g., us-ashburn-1, eu-frankfurt-1)
export OCI_REGION="us-ashburn-1"
echo "Using Compartment: $COMPARTMENT_OCID"
echo "Using VCN: $VCN_OCID"
echo "Using LB Subnet: $LB_SUBNET_OCID"
echo "Using Instance Subnet: $INSTANCE_SUBNET_OCID"
echo "Using Availability Domain: $AVAILABILITY_DOMAIN"
echo "Using SSH Public Key from: $SSH_PUBLIC_KEY_FILE"
echo "Using Region: $OCI_REGION"
Note: For production environments, it's highly recommended to deploy instance pools across multiple Availability Domains or Fault Domains within a single AD to maximize high availability and resilience.
Step-by-Step Implementation
1. Prepare Custom Image or Select a Public Image
For this walkthrough, we'll use a public Oracle Linux 8 image and leverage cloud-init to install and configure Nginx upon instance launch. This simulates a common scenario where you start with a base image and inject application-specific configurations.
First, let's find the OCID of a suitable public image. We'll look for Oracle Linux 8 in our compartment.
export IMAGE_OCID=$(oci compute image list \
--compartment-id $COMPARTMENT_OCID \
--operating-system "Oracle Linux" \
--operating-system-version "8" \
--query "data[0].id" \
--raw-output)
if [ -z "$IMAGE_OCID" ]; then
echo "Error: Could not find an Oracle Linux 8 image in compartment $COMPARTMENT_OCID. Please check your compartment or image availability."
exit 1
fi
echo "Using Image OCID: $IMAGE_OCID"
2. Create an Instance Configuration
An Instance Configuration serves as a template for creating instances within an instance pool. It defines the instance shape, image, network settings, SSH keys, and any cloud-init scripts to run on launch.
We'll use a simple cloud-init script to install Nginx and create a basic index.html file to confirm the web server is running.
export INSTANCE_CONFIG_NAME="MyWebServerInstanceConfig"
export INSTANCE_SHAPE="VM.Standard.E4.Flex" # Choose an appropriate shape
# Cloud-init script to install Nginx
# Base64 encode the script for use in the CLI command
export CLOUD_INIT_SCRIPT=$(cat <<EOF
#!/bin/bash
sudo yum update -y
sudo yum install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx
echo "Hello from OCI Instance Pool instance \$(hostname)!" | sudo tee /usr/share/nginx/html/index.html
EOF
)
export CLOUD_INIT_SCRIPT_BASE64=$(echo "$CLOUD_INIT_SCRIPT" | base64)
echo "Creating Instance Configuration..."
export INSTANCE_CONFIG_OCID=$(oci compute-management instance-configuration create \
--compartment-id $COMPARTMENT_OCID \
--display-name "$INSTANCE_CONFIG_NAME" \
--instance-details '{
"instanceType": "compute",
"launchDetails": {
"compartmentId": "'"$COMPARTMENT_OCID"'",
"shape": "'"$INSTANCE_SHAPE"'",
"imageId": "'"$IMAGE_OCID"'",
"createVnicDetails": {
"subnetId": "'"$INSTANCE_SUBNET_OCID"'",
"assignPublicIp": false,
"skipSourceDestCheck": false
},
"metadata": {
"ssh_authorized_keys": "'"$SSH_PUBLIC_KEY"'",
"user_data": "'"$CLOUD_INIT_SCRIPT_BASE64"'"
},
"sourceDetails": {
"sourceType": "image",
"imageId": "'"$IMAGE_OCID"'"
}
}
}' \
--query "data.id" \
--raw-output)
if [ -z "$INSTANCE_CONFIG_OCID" ]; then
echo "Error: Failed to create Instance Configuration."
exit 1
fi
echo "Instance Configuration created with OCID: $INSTANCE_CONFIG_OCID"
3. Create an Instance Pool
An Instance Pool manages a group of instances based on the instance configuration. We'll start with a small initial size.
export INSTANCE_POOL_NAME="MyWebServerInstancePool"
export INSTANCE_POOL_SIZE=1 # Initial number of instances
echo "Creating Instance Pool..."
export INSTANCE_POOL_OCID=$(oci compute-management instance-pool create \
--compartment-id $COMPARTMENT_OCID \
--display-name "$INSTANCE_POOL_NAME" \
--instance-configuration-id "$INSTANCE_CONFIG_OCID" \
--placement-configurations '[
{
"availabilityDomain": "'"$AVAILABILITY_DOMAIN"'",
"primarySubnetId": "'"$INSTANCE_SUBNET_OCID"'"
}
]' \
--size "$INSTANCE_POOL_SIZE" \
--query "data.id" \
--raw-output)
if [ -z "$INSTANCE_POOL_OCID" ]; then
echo "Error: Failed to create Instance Pool."
exit 1
fi
echo "Instance Pool created with OCID: $INSTANCE_POOL_OCID"
echo "Waiting for Instance Pool to become active (this may take a few minutes)..."
oci compute-management instance-pool get --instance-pool-id "$INSTANCE_POOL_OCID" --query "data.lifecycle-state" --raw-output
oci compute-management instance-pool wait --instance-pool-id "$INSTANCE_POOL_OCID" --lifecycle-state RUNNING
echo "Instance Pool is RUNNING."
4. Create an Autoscaling Configuration
Now, we'll define the rules for scaling the instance pool. We'll set up a policy to scale out when CPU utilization exceeds 60% and scale in when it drops below 20%. The minimum size will be 1, and the maximum will be 3 for demonstration purposes.
export AUTOSCALING_CONFIG_NAME="MyWebServerAutoscalingConfig"
export AUTOSCALING_MIN_SIZE=1
export AUTOSCALING_MAX_SIZE=3
export AUTOSCALING_INITIAL_SIZE=1
echo "Creating Autoscaling Configuration..."
export AUTOSCALING_CONFIG_OCID=$(oci compute-management autoscaling-configuration create \
--compartment-id $COMPARTMENT_OCID \
--display-name "$AUTOSCALING_CONFIG_NAME" \
--resource '{
"id": "'"$INSTANCE_POOL_OCID"'",
"type": "instancePool"
}' \
--auto-scaling-policies '[
{
"displayName": "CpuUtilizationPolicy",
"policyType": "metric",
"isEnabled": true,
"capacity": {
"initial": '$AUTOSCALING_INITIAL_SIZE',
"min": '$AUTOSCALING_MIN_SIZE',
"max": '$AUTOSCALING_MAX_SIZE'
},
"rules": [
{
"displayName": "ScaleOutRule",
"action": {
"type": "CHANGE_COUNT_BY",
"value": 1
},
"metric": {
"metricType": "CPU_UTILIZATION",
"threshold": {
"operator": "GT",
"value": 60.0
}
},
"periodInSeconds": 300,
"evaluationDurationInSeconds": 300
},
{
"displayName": "ScaleInRule",
"action": {
"type": "CHANGE_COUNT_BY",
"value": -1
},
"metric": {
"metricType": "CPU_UTILIZATION",
"threshold": {
"operator": "LT",
"value": 20.0
}
},
"periodInSeconds": 300,
"evaluationDurationInSeconds": 300
}
]
}
]' \
--query "data.id" \
--raw-output)
if [ -z "$AUTOSCALING_CONFIG_OCID" ]; then
echo "Error: Failed to create Autoscaling Configuration."
exit 1
fi
echo "Autoscaling Configuration created with OCID: $AUTOSCALING_CONFIG_OCID"
5. Create a Load Balancer
The Load Balancer will distribute traffic to our instance pool. We'll create a public load balancer.
export LB_NAME="MyWebServerLoadBalancer"
export LB_SHAPE="10Mbps" # Smallest shape for demonstration
echo "Creating Load Balancer..."
export LB_OCID=$(oci lb load-balancer create \
--compartment-id $COMPARTMENT_OCID \
--display-name "$LB_NAME" \
--shape-name "$LB_SHAPE" \
--subnet-ids '["'"$LB_SUBNET_OCID"'"]' \
--query "data.id" \
--raw-output)
if [ -z "$LB_OCID" ]; then
echo "Error: Failed to create Load Balancer."
exit 1
fi
echo "Load Balancer created with OCID: $LB_OCID"
echo "Waiting for Load Balancer to become active (this may take a few minutes)..."
oci lb load-balancer get --load-balancer-id "$LB_OCID" --query "data.lifecycle-state" --raw-output
oci lb load-balancer wait --load-balancer-id "$LB_OCID" --lifecycle-state ACTIVE
echo "Load Balancer is ACTIVE."
export LB_IP_ADDRESS=$(oci lb load-balancer get --load-balancer-id "$LB_OCID" --query "data.ip-addresses[0].ip-address" --raw-output)
echo "Load Balancer IP Address: $LB_IP_ADDRESS"
6. Integrate Instance Pool with Load Balancer Backend Set
Now we connect the instance pool to the load balancer by creating a backend set and then adding the instance pool as its source of backend servers.
First, create a Backend Set. This defines how the Load Balancer communicates with the backend instances, including health checks.
export BACKEND_SET_NAME="WebServerBackendSet"
export HEALTH_CHECK_PATH="/index.html" # Path for Nginx default page
echo "Creating Load Balancer Backend Set..."
oci lb backend-set create \
--load-balancer-id "$LB_OCID" \
--name "$BACKEND_SET_NAME" \
--policy "ROUND_ROBIN" \
--health-checker '{
"protocol": "HTTP",
"port": 80,
"urlPath": "'"$HEALTH_CHECK_PATH"'",
"retries": 3,
"timeoutInMilliSeconds": 5000,
"intervalInMilliSeconds": 10000
}' \
--port 80 \
--wait-for-state ACTIVE
echo "Backend Set '$BACKEND_SET_NAME' created."
Next, populate the Backend Set with the Instance Pool. This is the crucial step that links the dynamic instance pool to the load balancer.
echo "Adding Instance Pool to Backend Set..."
oci lb backend-set populate-from-instance-pool \
--load-balancer-id "$LB_OCID" \
--backend-set-name "$BACKEND_SET_NAME" \
--instance-pool-id "$INSTANCE_POOL_OCID" \
--port 80 \
--wait-for-state ACTIVE
echo "Instance Pool '$INSTANCE_POOL_NAME' added to Backend Set '$BACKEND_SET_NAME'."
Finally, create a Listener. The listener defines the port and protocol that the Load Balancer listens on for incoming traffic and directs it to the appropriate backend set.
export LISTENER_NAME="HttpListener"
echo "Creating Load Balancer Listener..."
oci lb listener create \
--load-balancer-id "$LB_OCID" \
--name "$LISTENER_NAME" \
--port 80 \
--protocol "HTTP" \
--default-backend-set-name "$BACKEND_SET_NAME" \
--wait-for-state ACTIVE
echo "Listener '$LISTENER_NAME' created."
7. Update Security Lists / Network Security Groups
For the Load Balancer and instances to communicate, and for external traffic to reach the Load Balancer, we need to ensure the VCN's security lists (or NSGs) are correctly configured.
- Load Balancer Subnet Security List:
- Ingress: Allow TCP port 80 (HTTP) from 0.0.0.0/0 (Internet).
- Egress: Allow TCP port 80 to the Instance Subnet CIDR (for traffic to instances).
- Instance Subnet Security List:
- Ingress: Allow TCP port 80 from the Load Balancer Subnet CIDR (for traffic from LB).
- Ingress: Allow TCP port 22 (SSH) from your trusted IP range (for management).
- Egress: Allow all TCP traffic to 0.0.0.0/0 (for updates, OCI services, etc., or restrict as needed).
Let's assume you have existing security lists. We'll add the necessary ingress rules. First, get the security list OCIDs for your subnets.
export LB_SUBNET_SECURITY_LIST_OCID=$(oci network subnet get --subnet-id "$LB_SUBNET_OCID" --query "data.security-list-ids[0]" --raw-output)
export INSTANCE_SUBNET_SECURITY_LIST_OCID=$(oci network subnet get --subnet-id "$INSTANCE_SUBNET_OCID" --query "data.security-list-ids[0]" --raw-output)
echo "LB Subnet Security List OCID: $LB_SUBNET_SECURITY_LIST_OCID"
echo "Instance Subnet Security List OCID: $INSTANCE_SUBNET_SECURITY_LIST_OCID"
# Add Ingress Rule for HTTP to LB Subnet
echo "Updating LB Subnet Security List to allow HTTP (port 80) from Internet..."
oci network security-list update \
--security-list-id "$LB_SUBNET_SECURITY_LIST_OCID" \
--ingress-security-rules '[
{
"protocol": "6",
"source": "0.0.0.0/0",
"sourceType": "CIDR_BLOCK",
"tcpOptions": {
"destinationPortRange": {
"max": 80,
"min": 80
}
}
}
]' \
--force
# Add Ingress Rule for HTTP from LB to Instance Subnet
echo "Updating Instance Subnet Security List to allow HTTP (port 80) from LB subnet..."
oci network security-list update \
--security-list-id "$INSTANCE_SUBNET_SECURITY_LIST_OCID" \
--ingress-security-rules '[
{
"protocol": "6",
"source": "'$(oci network subnet get --subnet-id "$LB_SUBNET_OCID" --query "data.cidr-block" --raw-output)'",
"sourceType": "CIDR_BLOCK",
"tcpOptions": {
"destinationPortRange": {
"max": 80,
"min": 80
}
}
},
{
"protocol": "6",
"source": "0.0.0.0/0",
"sourceType": "CIDR_