OCI Functions runs your code without any server management, scaling from zero to thousands of concurrent executions automatically. The practical challenges are cold start latency for latency-sensitive workloads, concurrency control to prevent runaway execution, retry patterns for unreliable event sources, and observability when functions are invisible in your monitoring. This post covers provisioned concurrency, reserved concurrency, APM tracing, and dead letter queue patterns with Terraform.
Step 1: Application and Function with IAM
resource "oci_identity_dynamic_group" "functions_runners" {
compartment_id = var.tenancy_ocid
name = "functions-dynamic-group"
description = "OCI Functions execution principals"
matching_rule = "All {resource.type = 'fnfunc', resource.compartment.id = 'COMPARTMENT_OCID'}"
}
resource "oci_identity_policy" "functions_policy" {
compartment_id = var.compartment_id
name = "functions-execution-policy"
statements = [
"Allow dynamic-group functions-dynamic-group to read secret-bundle in compartment id COMPARTMENT_OCID",
"Allow dynamic-group functions-dynamic-group to manage objects in compartment id COMPARTMENT_OCID",
"Allow dynamic-group functions-dynamic-group to use ons-topics in compartment id COMPARTMENT_OCID",
"Allow dynamic-group functions-dynamic-group to manage queues in compartment id COMPARTMENT_OCID"
]
}
resource "oci_functions_application" "orders_app" {
compartment_id = var.compartment_id
display_name = "orders-processing-app"
subnet_ids = [var.functions_subnet_id]
shape = "GENERIC_X86"
config = {
DB_HOST = var.db_host
REGION = var.region
LOG_LEVEL = "INFO"
QUEUE_ENDPOINT = var.queue_endpoint
}
trace_config {
is_enabled = true
domain_id = var.apm_domain_id
}
defined_tags = { "Operations.Environment" = "production", "Operations.ManagedBy" = "terraform" }
}
resource "oci_functions_function" "process_order" {
application_id = oci_functions_application.orders_app.id
display_name = "process-order"
image = "me-jeddah-1.ocir.io/NAMESPACE/production/process-order:2.1.0"
memory_in_mbs = 512
timeout_in_seconds = 120
# Provisioned concurrency - pre-warms instances to eliminate cold start
provisioned_concurrency_config {
strategy = "CONSTANT"
count = 5 # Keep 5 warm instances ready at all times
}
config = {
MAX_RETRY_COUNT = "3"
DLQ_QUEUE_ID = var.orders_dlq_queue_id
}
defined_tags = { "Operations.Environment" = "production" }
}
output "function_id" { value = oci_functions_function.process_order.id }
Step 2: OCI Queue as DLQ for Failed Invocations
resource "oci_queue_queue" "orders_processing_queue" {
compartment_id = var.compartment_id
display_name = "orders-processing-queue"
visibility_in_seconds = 30
timeout_in_seconds = 120
retention_in_seconds = 86400
max_delivery_count = 3
dead_letter_queue_delivery_count = 3
defined_tags = { "Operations.ManagedBy" = "terraform" }
}
resource "oci_queue_queue" "orders_dlq" {
compartment_id = var.compartment_id
display_name = "orders-processing-dlq"
visibility_in_seconds = 30
timeout_in_seconds = 600
retention_in_seconds = 604800 # 7 days
max_delivery_count = 1
}
resource "oci_monitoring_alarm" "dlq_messages_arrived" {
compartment_id = var.compartment_id
display_name = "orders-dlq-messages-alert"
is_enabled = true
metric_compartment_id = var.compartment_id
namespace = "oci_queue"
query = "ApproximateNumberOfMessages[5m]{queueId = 'DLQ_OCID'}.max() > 0"
severity = "CRITICAL"
pending_duration = "PT5M"
destinations = [var.ops_notification_topic_id]
body = "Messages in the orders DLQ. The process-order function has exceeded max retry count. Investigate function errors in APM traces."
}
Step 3: Multi-Trigger Setup
resource "oci_functions_function" "remediate_bucket" {
application_id = oci_functions_application.orders_app.id
display_name = "remediate-public-bucket"
image = "me-jeddah-1.ocir.io/NAMESPACE/tools/remediate-bucket:1.0.2"
memory_in_mbs = 256
timeout_in_seconds = 30
# No provisioned concurrency needed - event-triggered, latency not critical
provisioned_concurrency_config {
strategy = "NONE"
}
}
# Trigger: OCI Events routes public bucket creation to this function
resource "oci_events_rule" "public_bucket_trigger" {
compartment_id = var.compartment_id
display_name = "trigger-remediate-on-public-bucket"
is_enabled = true
condition = jsonencode({
"eventType" = ["com.oraclecloud.objectstorage.createbucket"]
"data" = { "publicAccessType" = ["ObjectRead", "ObjectReadWithoutList"] }
})
actions {
actions {
action_type = "FAAS"
is_enabled = true
function_id = oci_functions_function.remediate_bucket.id
}
}
}
Operational Notes
Provisioned concurrency eliminates cold starts by keeping a fixed number of function instances initialized and ready. You pay for the reserved instances even when they are not processing invocations. Use it for user-facing functions where cold start adds noticeable latency. Do not use it for background processing or event-triggered functions where cold start is acceptable. Set the count to your p99 concurrent invocation count, not the peak, to avoid over-provisioning.
Dead letter queues catch messages that fail after all retry attempts. Without a DLQ, a message that causes the function to error repeatedly is dropped after max retries. With a DLQ, the failed message is preserved for investigation and reprocessing. Monitor the DLQ depth with an alarm set to trigger on any message arrival, since messages in the DLQ always indicate a processing failure that needs attention.
Regards,
Osama
#OCI #OracleCloud #Functions #Serverless #Terraform #IaC #TechBlog #Oracle #PlatformEngineering #CloudNative #DevOps #OracleCloudInfrastructure #ColdStart #ProvisionedConcurrency #EventDriven #Queue #DLQ #APM #Observability #FaaS
Leave a Reply