Agent Observability Platform: Configuration Reference (Azure)

Configuration reference for the Agent Observability data platform on Azure

☁️

This page covers Azure. Also available for AWS and GCP.

Optional configuration β€” TLS, retention, ClickHouse users, evaluation, and sizing.

All settings on this page are optional β€” the defaults produce a working deployment. Set them as inputs to the Terraform module.

Data retention (TTL)

ClickHouse retains trace data for a fixed window, after which old data is dropped automatically.

InputDefaultDescription
clickhouse_ttl_days30Days of trace data to retain. Governs the trace tables and their conversation-derived annotation tables. Applied cluster-wide by the schema job, so retention can't drift between replicas.

ClickHouse users

The platform provisions a least-privilege ClickHouse user model β€” one SQL user per access path, each scoped to exactly the operations it performs:

UserAccessUsed by
schema_ownerRead/write on the telemetry database, plus DDL (including cluster-wide ON CLUSTER statements) and system-table maintenanceThe schema-migration job (creates tables, applies TTLs) and the owner (DEFINER) of the materialized views
otelINSERT-only into the raw trace tableThe OpenTelemetry Collector. Normalization still works because the materialized views run as schema_owner, not otel
llm_workerRead/append on the LLM job-queue tablesThe LLM worker
monte_carloRead-only on telemetry, plus append to the job-queue, evaluation-score, and conversation-annotation tablesThe Monte Carlo Agent (Trace Exploration, agent monitors, queuing evaluations) β€” this is the user you connect Monte Carlo as
probesystem.replicas replication health and SHOW TABLES on the telemetry database (passwordless; no row-level data reads)The ClickHouse readiness probe β€” needs no secret or configuration
readonly_userSELECT-only (optional)External SQL clients (e.g. DataGrip), MCP, and the deployment verification script
adminFull superuser, including access management (optional, gated)Break-glass DBA access β€” reachable only over loopback (pod-exec) by default

schema_owner, otel, llm_worker, monte_carlo, and probe are always provisioned. readonly_user and admin are opt-in.

Each user's password is generated by the module and stored in Azure Key Vault (RBAC-authorized, purge protection enabled), then synced into the cluster by the External Secrets Operator. Each user has its own secret, exposed as a clickhouse_<user>_credentials_secret_id output. To supply your own passwords instead, set the sensitive clickhouse_passwords input β€” note the values then also live in your Terraform state. The exception is probe, which is deliberately passwordless β€” its consumer is the kubelet's readiness check, which can't read a Kubernetes secret β€” and so has no Key Vault entry; what it can read is limited to replication-health metadata and table names, with no row data exposed.

πŸ“˜

The stock ClickHouse default superuser is removed during deployment as a hardening measure β€” every client authenticates as one of the users above.

Read-only user

To provision the optional readonly_user (SELECT-only) for external SQL clients:

helm = {
  chart_registry = "oci://registry-1.docker.io/montecarlodata"
  chart_version  = "4.0.0"

  clickhouse = {
    readonly_user = {
      enabled = true
    }
  }
}

Its password's Key Vault secret ID is exposed as the clickhouse_readonly_user_credentials_secret_id output. Remember that external clients reach ClickHouse through the Gateway over HTTPS on port 8443 β€” configure JDBC/HTTP clients accordingly (the native TCP protocol is not exposed).

Break-glass admin user

The admin superuser is opt-in and, by default, reachable only over loopback (pod-exec) β€” it is never on a network path. Enable it for break-glass DBA access:

helm = {
  chart_registry = "oci://registry-1.docker.io/montecarlodata"
  chart_version  = "4.0.0"

  clickhouse = {
    admin = {
      enabled = true
    }
  }
}

Its password's Key Vault secret ID is exposed as the clickhouse_admin_credentials_secret_id output (null when disabled).

Resource sizing

Each managed workload exposes optional Kubernetes resource requests/limits via helm.<workload>.resources. Omit a workload (or a requests/limits map) to use the chart defaults β€” start modest in development and tune up for production.

helm = {
  chart_registry = "oci://registry-1.docker.io/montecarlodata"
  chart_version  = "4.0.0"

  clickhouse = {
    resources = {
      requests = { cpu = "2", memory = "8Gi" }
      limits   = { cpu = "4", memory = "16Gi" }
    }
  }
  opentelemetry_collector = {
    resources = {
      requests = { memory = "2Gi" }
      limits   = { memory = "6Gi" }
    }
  }
  llm_worker = {
    resources = {
      requests = { cpu = "500m", memory = "1Gi" }
      limits   = { cpu = "2", memory = "4Gi" }
    }
  }
}

requests and limits are maps keyed by Kubernetes resource name (cpu, memory, ephemeral-storage, etc.); either can be omitted independently.

There are no replica-count overrides for the Collector or the LLM worker on Azure β€” both run at the chart's defaults. ClickHouse replicas are controlled by clickhouse_replica_count (see High availability below).

Node pools (new-cluster path)

On the new-cluster path, you can size the node pools the workloads run on:

InputDefaultDescription
cluster.vm_sizeStandard_D4s_v5VM size for the main node pool (Collector, LLM worker, controllers).
cluster.main_node_pool_size2Desired/minimum size of the main node pool (set 1 for a cost-optimized single node; max 10).
cluster.main_node_pool_max_size10Autoscaler ceiling for the main node pool.
clickhouse_node_pool.vm_sizeStandard_E8s_v5VM size for the per-zone ClickHouse node pools.
keeper_node_pool.vm_sizeStandard_D2as_v4VM size for the per-zone Keeper node pools (deliberately non-burstable β€” a throttled quorum voter risks spurious leader elections; avoid B-series).
keeper_node_pool.storage_size / .storage_class10Gi / managed-csiThe Keeper persistent volume (Raft log and snapshots only).

Node images are pinned by default (node-OS upgrade channel None); roll the stateless pool deliberately by setting or changing node_image_upgrade_token, and upgrade the stateful pools on your own schedule β€” see High availability β€” node-image updates. The cluster's Kubernetes version is pinned via kubernetes_version (default "1.35").

High availability

The topology inputs below deploy the standard HA shape β€” how it works, the dev/testing single-replica variant, and day-2 operations are covered on the High availability page.

InputDefaultDescription
clickhouse_zones[]Zone numbers for the per-zone ClickHouse node pools; the list length caps the replica count. Required (non-empty) on the new-cluster path β€” a single zone is the dev floor; must stay empty on an existing cluster.
keeper_zones[]Zone numbers for the Keeper voter node pools β€” must be an odd count (3 standard, 1 for dev). Also sets the chart's Keeper replica count. Required (non-empty) on the new-cluster path; must stay empty on an existing cluster.
clickhouse_replica_count1ClickHouse replicas (2 for the standard HA shape). Terraform-owned: a chart version bump alone never changes it.

Storage

ClickHouse data is stored on an Azure managed-disk persistent volume.

InputDefaultDescription
helm.clickhouse.storage_size500GiSize of the ClickHouse persistent volume.
clickhouse_storage_classclickhouse-premium-v2StorageClass for the ClickHouse volume. The module creates a dedicated clickhouse-premium-v2 class by default (Premium SSD v2, reclaimPolicy: Retain, volume expansion allowed); set to an existing class name to use your own β€” the module won't create it, and the PVC stays Pending if it doesn't exist.
storage_class_clickhouse_premiumv2.iops3000Provisioned IOPS for the clickhouse-premium-v2 class (min 3000, max 80000; the default is the free baseline).
storage_class_clickhouse_premiumv2.throughput125Throughput in MB/s for the clickhouse-premium-v2 class (min 125, max 1200; the default is the free baseline).
⚠️

Premium SSD v2 availability varies by region and zone β€” verify support in your target region and zones before deploying, or point clickhouse_storage_class at a different class. A StatefulSet's storageClassName is immutable: existing deployments keep their original class, so choose deliberately on the first apply.

Network access

The platform's endpoints sit on a single managed Gateway backed by an internal load balancer β€” nothing is exposed to the public internet. The controls below govern who can reach what:

InputDefaultDescription
gateway.allowed_source_rangesnull (unrestricted)Source ranges permitted to reach the Gateway's internal load balancer. null = no restriction; [] = the VNet address space only; a CIDR list = the VNet space plus the listed ranges (the VNet space is always folded in, so a restriction never locks out in-VNet clients β€” on a bring-your-own VNet, include your address space explicitly). One unioned list, since both endpoints share the Gateway's load balancer.
cluster.api_server_authorized_ip_ranges[]Allow-list for the AKS API server's public endpoint (new-cluster path). Mutually exclusive with private_cluster_enabled.
cluster.private_cluster_enabledfalseFully private AKS control plane β€” no public API endpoint (new-cluster path). Every apply and kubectl then needs a private path: VPN, peering, a private endpoint, or an in-VNet runner.
cluster.local_account_disabledtrueDisables the legacy certificate-based cluster admin account β€” all access goes through Entra ID and Azure RBAC. Set false only to pre-stage a break-glass kubeconfig for an identity-provider outage.
❗️

The Gateway source ranges are the primary network control for reaching ClickHouse β€” the network-facing ClickHouse users accept connections from any source at the ClickHouse layer. This includes the passwordless probe user (metadata-only access to replication health and table names) β€” one more reason to restrict gateway.allowed_source_ranges. (The admin superuser is the exception: it is loopback-only and never reachable through the Gateway.) Leaving the default unrestricted means anyone who can route to the internal load balancer can attempt to connect. Set it before enabling readonly_user for external SQL clients.

Two further network facts worth knowing:

  • Egress is a NAT gateway. Module-created VNets route outbound traffic through a NAT gateway with a static public IP, so pulls from Docker Hub, Let's Encrypt validation, and Foundry calls present one stable source address.
  • etcd is encrypted with your key. New clusters enable AKS KMS etcd encryption with a customer-managed key in the module's Key Vault.

TLS

TLS is enabled by default and terminates on the managed Gateway. cert-manager issues publicly-trusted Let's Encrypt certificates for your otel_collector_domain and clickhouse_domain via DNS-01 validation against dns_zone, and renews them automatically β€” there are no certificate resources to manage or rotate yourself. Behind the Gateway, traffic is re-encrypted to the Collector's and ClickHouse's in-cluster certificates (issued by cert-manager, validated via trust-manager), so it is encrypted end to end.

InputDefaultDescription
gateway.letsencrypt_emailnullOptional ACME contact address for the Let's Encrypt account. Omit for a contactless account β€” certificates still auto-renew.

The Gateway is HTTP-family (L7) only: telemetry is ingested as OTLP over HTTP (not gRPC), and ClickHouse is served over its HTTPS interface on port 8443 β€” the native TCP protocol is not exposed. "Publicly trusted" describes the certificate chain; the endpoints stay on a private IP.

Evaluation (Microsoft Foundry)

The LLM worker performs evaluation by calling Claude on a Microsoft Foundry resource the module provisions β€” an AIServices account, a project, and one deployment per curated Claude model, with a custom least-privilege inference role granted to the worker's Workload Identity. Authentication is Entra-only (disableLocalAuth = true β€” no API keys anywhere), over the account's public endpoint.

InputDefaultDescription
helm.llm_worker.foundry.organization_nameβ€” (required)Your organization's name β€” the Azure Marketplace attestation for the Anthropic pay-per-token offer.
helm.llm_worker.foundry.account_name"<cluster name>-foundry"The Foundry account name β€” also its globally unique subdomain (<name>.services.ai.azure.com). Set a distinct value per deployment, or a name collision fails the apply.
helm.llm_worker.foundry.locationthe module's locationAzure region for the Foundry account and its model deployments. Set it when Claude on Foundry is offered in a region other than the one hosting the cluster.
helm.llm_worker.foundry.country_code / .industry"US" / "technology"Attestation fields recorded with the Marketplace offer (industry is lowercase).
helm.llm_worker.foundry.default_capacity100Capacity per model deployment, in units of 1,000 tokens per minute (100 = 100,000 TPM, GlobalStandard). Lower it if the default exceeds the Claude quota granted in your subscription.
helm.llm_worker.foundry.deploymentshaiku / sonnet / opusThe curated Claude model deployments β€” defaults: claude-haiku-4-5, claude-sonnet-5, claude-opus-4-8. Each entry supports name (the Claude model ID, which doubles as the deployment name), optional version, and optional per-deployment capacity.
helm.llm_worker.image_repositorymontecarlodata/ao-llm-workerOverride the LLM-worker image repository.
helm.llm_worker.image_taglatest-azureLLM-worker image tag. latest-azure is a floating tag β€” pin to a published version for production; the current release is 1.1.0-azure.

Claude model availability and tokens-per-minute quota vary by region and subscription β€” verify both for the Foundry region (helm.llm_worker.foundry.location, defaulting to your location) in the Azure portal before relying on the defaults, and check current model IDs there if a deployment fails to create.

The account name, account ID, project name, and project ID are exposed as the foundry_account_name, foundry_account_id, foundry_project_name, and foundry_project_id outputs.

Ingestion into the platform is OTLP push only β€” there is no object-storage (blob) ingest path on Azure, and the Collector runs with no cloud identity at all.


Did this page help you?