OCI Service Mesh with OKE: mTLS, Traffic Shifting, and Access Policies with Terraform

When you have more than a handful of microservices talking to each other, the operational questions go beyond whether services are up. You need to know whether encryption is enforced on every service-to-service call, whether you can shift traffic between versions without changing deployment manifests, and whether unauthorized service pairs are blocked by default. A service mesh handles all three.

OCI Service Mesh is Oracle’s managed Envoy-based mesh integrated with OKE. It enforces mTLS using certificates from OCI Certificates, provides traffic management through virtual deployments and routing rules, and applies access policies as an explicit allowlist. This post covers the full setup with Terraform, from mesh creation through canary traffic splits and zero-trust access policies.

Step 1: Create the Mesh

resource "oci_service_mesh_mesh" "production" {
  compartment_id = var.compartment_id
  display_name   = "production-service-mesh"

  certificate_authorities {
    id = var.oci_certificates_ca_id
  }

  # Start PERMISSIVE during onboarding
  # Switch to STRICT per virtual service once all proxies are enrolled
  mtls {
    minimum = "PERMISSIVE"
  }

  defined_tags = {
    "Operations.Environment" = "production"
    "Operations.ManagedBy"   = "terraform"
  }
}

Step 2: Virtual Services with STRICT mTLS

resource "oci_service_mesh_virtual_service" "orders_api" {
  compartment_id = var.compartment_id
  mesh_id        = oci_service_mesh_mesh.production.id
  name           = "orders-api"
  hosts          = ["orders-api", "orders-api.orders.svc.cluster.local"]

  default_routing_policy { type = "UNIFORM" }
  mtls { mode = "STRICT" }
}

resource "oci_service_mesh_virtual_service" "inventory_api" {
  compartment_id = var.compartment_id
  mesh_id        = oci_service_mesh_mesh.production.id
  name           = "inventory-api"
  hosts          = ["inventory-api", "inventory-api.inventory.svc.cluster.local"]

  default_routing_policy { type = "UNIFORM" }
  mtls { mode = "STRICT" }
}

Step 3: Virtual Deployments and Canary Traffic Split

resource "oci_service_mesh_virtual_deployment" "orders_v1" {
  compartment_id     = var.compartment_id
  virtual_service_id = oci_service_mesh_virtual_service.orders_api.id
  name               = "orders-v1"

  listeners {
    protocol = "HTTP"
    port     = 8080
  }

  service_discovery {
    type     = "DNS"
    hostname = "orders-v1.orders.svc.cluster.local"
  }
}

resource "oci_service_mesh_virtual_deployment" "orders_v2" {
  compartment_id     = var.compartment_id
  virtual_service_id = oci_service_mesh_virtual_service.orders_api.id
  name               = "orders-v2"

  listeners {
    protocol = "HTTP"
    port     = 8080
  }

  service_discovery {
    type     = "DNS"
    hostname = "orders-v2.orders.svc.cluster.local"
  }
}

# Start: 90% stable v1, 10% canary v2
resource "oci_service_mesh_virtual_service_route_table" "orders_routes" {
  compartment_id     = var.compartment_id
  virtual_service_id = oci_service_mesh_virtual_service.orders_api.id
  name               = "orders-route-table"
  priority           = 1

  route_rules {
    type      = "HTTP"
    is_grpc   = false
    path      = "/"
    path_type = "PREFIX"

    destinations {
      virtual_deployment_id = oci_service_mesh_virtual_deployment.orders_v1.id
      weight                = 90
      port                  = 8080
    }

    destinations {
      virtual_deployment_id = oci_service_mesh_virtual_deployment.orders_v2.id
      weight                = 10
      port                  = 8080
    }
  }
}

Traffic weight updates via Terraform are near-instant with no pod restarts. The Envoy proxies pick up new routing rules from the control plane within seconds. Shift from 10 to 25 to 50 to 100 percent over time, watching error rates at each step, and roll back instantly by reverting weights and re-applying.

Step 4: Zero-Trust Access Policies

resource "oci_service_mesh_access_policy" "orders_to_inventory" {
  compartment_id = var.compartment_id
  mesh_id        = oci_service_mesh_mesh.production.id
  name           = "orders-to-inventory"

  rules {
    action = "ALLOW"
    source      { type = "VIRTUAL_SERVICE"; virtual_service_id = oci_service_mesh_virtual_service.orders_api.id }
    destination { type = "VIRTUAL_SERVICE"; virtual_service_id = oci_service_mesh_virtual_service.inventory_api.id }
  }
}

# Default deny: any unlisted service pair is blocked
resource "oci_service_mesh_access_policy" "deny_all" {
  compartment_id = var.compartment_id
  mesh_id        = oci_service_mesh_mesh.production.id
  name           = "deny-all-default"
  priority       = 1000

  rules {
    action      = "DENY"
    source      { type = "ALL_VIRTUAL_SERVICES" }
    destination { type = "ALL_VIRTUAL_SERVICES" }
  }
}

Step 5: Monitoring Alarms

resource "oci_monitoring_alarm" "mesh_5xx_errors" {
  compartment_id        = var.compartment_id
  display_name          = "service-mesh-5xx-errors"
  is_enabled            = true
  metric_compartment_id = var.compartment_id
  namespace             = "oci_service_mesh"
  query                 = "RequestCount[5m]{responseCode = '5xx', meshId = '${oci_service_mesh_mesh.production.id}'}.sum() > 50"
  severity              = "CRITICAL"
  pending_duration      = "PT5M"
  destinations          = [var.ops_notification_topic_id]
  body                  = "Service mesh 5xx error rate is high. Check virtual service health and application logs."
}

resource "oci_monitoring_alarm" "mtls_failures" {
  compartment_id        = var.compartment_id
  display_name          = "mtls-handshake-failures"
  is_enabled            = true
  metric_compartment_id = var.compartment_id
  namespace             = "oci_service_mesh"
  query                 = "MtlsHandshakeFailures[5m]{meshId = '${oci_service_mesh_mesh.production.id}'}.sum() > 0"
  severity              = "WARNING"
  pending_duration      = "PT5M"
  destinations          = [var.security_notification_topic_id]
  body                  = "mTLS handshake failures in the service mesh. A service may have an expired or invalid certificate."
}

Operational Notes

Access policies work as an explicit allowlist. Without a DENY ALL default policy, any two services in the mesh can communicate even without an explicit ALLOW rule. The low-priority DENY ALL rule enforces zero-trust: every new service added to the mesh is isolated until an access policy explicitly permits it to connect to something.

The mesh sidecar adds roughly 1 to 3 milliseconds of latency per hop. Measure this in your environment using OCI APM tracing before and after enabling the mesh so you have a baseline. For most workloads this is negligible, but for latency-sensitive paths it is worth knowing the number before you enable STRICT mTLS across the entire service graph.

Regards,
Osama

#OCI #OracleCloud #ServiceMesh #OKE #Kubernetes #mTLS #Terraform #CloudNative #TechBlog #Oracle #DevOps #PlatformEngineering #Microservices #TrafficManagement #CanaryDeployment #Envoy #ZeroTrust #OracleCloudInfrastructure #IaC #CloudSecurity

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.