Agent Observability Platform: Installation (AWS)
Configure and apply the Terraform module to deploy the platform on AWS
PrerequisitesComplete the Prerequisites first β tooling, AWS permissions, domains, and chart registry access.
Overview
The terraform-aws-ao-data-platform module provisions the EKS cluster and, by default (helm.deploy_charts = true), also deploys the ao-data-platform Helm chart β ClickHouse, the OpenTelemetry Collector, and the LLM worker β in the same terraform apply.
The kubernetes and helm providers are configured from the module's outputs, which lets Terraform defer the Kubernetes/Helm resources until after the EKS cluster exists. This is what enables a single-pass apply.
Work through the steps below in order. The required inputs are the same for both cluster paths: region, otel_collector_domain, clickhouse_domain, helm.chart_registry, and helm.chart_version.
The examples below install the public artifacts: the Terraform module from the Terraform Registry (
monte-carlo-data/ao-data-platform/aws), and theao-data-platformHelm chart andao-llm-workerimage from Docker Hub (see Prerequisites). Pulling them requires no registry authentication.
1. Configure the providers
Your root module must configure the aws, kubernetes, and helm providers. The kubernetes and helm providers are wired from the module's outputs β substitute your region for us-east-1:
terraform {
required_providers {
aws = { source = "hashicorp/aws", version = "~> 6.0" }
kubernetes = { source = "hashicorp/kubernetes", version = "~> 2.0" }
helm = { source = "hashicorp/helm", version = "~> 2.0" }
}
}
provider "aws" {
region = "us-east-1"
}
provider "kubernetes" {
host = module.ao_data_platform.eks_cluster_endpoint
cluster_ca_certificate = module.ao_data_platform.eks_cluster_ca_certificate
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", module.ao_data_platform.eks_cluster_name, "--region", "us-east-1"]
}
}
provider "helm" {
kubernetes {
host = module.ao_data_platform.eks_cluster_endpoint
cluster_ca_certificate = module.ao_data_platform.eks_cluster_ca_certificate
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", module.ao_data_platform.eks_cluster_name, "--region", "us-east-1"]
}
}
}2. Configure the module
Choose the section for your deployment path:
New cluster
The module creates the VPC and EKS cluster with sensible defaults (cluster name monte-carlo). The example below deploys the standard highly available topology β two ClickHouse replicas across two AZs, coordinated by a three-voter Keeper ensemble β substitute your region's AZ names:
module "ao_data_platform" {
source = "monte-carlo-data/ao-data-platform/aws"
version = "2.3.0"
region = "us-east-1"
otel_collector_domain = "otel.acme.com"
clickhouse_domain = "clickhouse.acme.com"
hosted_zone_id = "Z1234567890ABC"
# Two ClickHouse replicas across two AZs, backed by a 3-voter Keeper ensemble.
clickhouse_availability_zones = ["us-east-1a", "us-east-1b"]
keeper_availability_zones = ["us-east-1a", "us-east-1b", "us-east-1c"]
clickhouse_replica_count = 2
helm = {
chart_registry = "oci://registry-1.docker.io/montecarlodata"
chart_version = "4.0.0"
llm_worker = {
image_tag = "1.1.0-aws"
}
}
}See examples/new_cluster/ for a complete, copy-paste starting point including the provider block.
Dev/testing: for an environment where AZ-failure tolerance isn't worth the node count, run a single replica with a single Keeper voter β see the single-replica variant. List AZs your VPC actually covers: the module's VPC spans the region's first three AZs by default, and each listed AZ must have a private subnet.
Existing cluster
Set cluster.create = false and networking.create_vpc = false, and provide your cluster name, VPC ID, and private subnet IDs in different AZs (one subnet per AZ β three AZs for the default HA topology):
module "ao_data_platform" {
source = "monte-carlo-data/ao-data-platform/aws"
version = "2.3.0"
region = "us-east-1"
cluster = {
create = false
existing_cluster_name = "my-cluster"
}
networking = {
create_vpc = false
existing_vpc_id = "vpc-0abc123"
existing_private_subnet_ids = ["subnet-aaa", "subnet-bbb", "subnet-ccc"]
}
otel_collector_domain = "otel.acme.com"
clickhouse_domain = "clickhouse.acme.com"
hosted_zone_id = "Z1234567890ABC"
helm = {
chart_registry = "oci://registry-1.docker.io/montecarlodata"
chart_version = "4.0.0"
llm_worker = {
image_tag = "1.1.0-aws"
}
# Set any controller already present in the cluster to false:
# install_aws_load_balancer_controller = false
# install_cert_manager = false
# install_external_secrets_operator = false
# install_external_dns = false
}
}See examples/existing_cluster/ for the full configuration. If the cluster's OIDC provider was created outside Terraform, import it first.
On an existing cluster the module does not create the ClickHouse and Keeper node groups (and rejects
keeper_availability_zonesat plan time) β you attach tainted per-AZ node groups yourself and take the self-managed Helm route (helm.deploy_charts = false) to wire the scheduling values, since the module-managed release doesn't expose them. See High availability β existing-cluster deployments.
helm.chart_registryis the registry prefix only βoci://registry-1.docker.io/montecarlodata, with no chart name. The module appends/ao-data-platformitself, so adding it here (.../montecarlodata/ao-data-platform) makesterraform applyfail to pull the chart. The artifacts table in Prerequisites lists the full chart path because that's the chart's location β but as a module input, pass only the prefix.
3. Initialize Terraform
terraform initThis downloads the module and the aws, kubernetes, and helm providers.
4. Review the plan
terraform planReview the planned changes before applying. On the new-cluster path the plan creates a VPC, the EKS cluster and node groups β including one per listed ClickHouse and Keeper AZ β IAM/IRSA roles, a KMS key, Secrets Manager secrets, ACM certificates, and the Helm releases.
Check the Availability Zones. EBS volumes are AZ-locked, so ClickHouse and Keeper placement is driven by the explicit AZ names you listed β see ClickHouse and Keeper node groups below. The module verifies at plan time that every listed AZ has a private subnet, and that
clickhouse_availability_zones[0]matches the AZ of any existing ClickHouse volume (on a fresh install this resolves to the region's first AZ). If the plan fails one of these preconditions, fix the AZ lists rather than overriding the guard.
5. Apply
terraform applyReview the plan once more, then confirm. The apply provisions the AWS infrastructure and (with helm.deploy_charts = true) deploys the chart in one pass. A full apply on the new-cluster path typically takes 15β25 minutes, most of it waiting on the EKS cluster and node groups.
applymodifies your~/.kube/config. To enable the single-pass deploy, the module runsaws eks update-kubeconfiginsidelocal-execprovisioners β it has tokubectl waitfor the External Secrets Operator CRDs and apply aClusterSecretStore, which the native Terraformkubernetes/helmproviders can't do for resources created in the same apply. This adds or refreshes the cluster's context in the~/.kube/configof the machine running Terraform and makes it the current context. It happens on every apply, on both cluster paths.
To manage the Helm release yourself instead, set
helm.deploy_charts = falseand follow the self-managed Helm install.
6. Confirm what was created
When the apply completes, review the outputs:
terraform outputYou should see (among others):
| Output | What it is |
|---|---|
eks_cluster_name | The cluster name β use it to configure kubectl next |
montecarlo_namespace | The namespace (montecarlo) all components run in |
clickhouse_monte_carlo_credentials_secret_arn | Secrets Manager ARN for the monte_carlo user β the credential you hand to Monte Carlo |
clickhouse_otel_credentials_secret_arn | ARN for the otel ingest user (OpenTelemetry Collector) |
clickhouse_schema_owner_credentials_secret_arn / clickhouse_llm_worker_credentials_secret_arn | ARNs for the schema_owner (migrations / MV owner) and llm_worker users |
clickhouse_admin_credentials_secret_arn / clickhouse_readonly_user_credentials_secret_arn | ARNs for the optional admin and readonly_user β null when those users are disabled |
otel_collector_irsa_role_arn / llm_worker_irsa_role_arn | IRSA roles for the workloads |
clickhouse_node_group | The legacy single-instance ClickHouse node group (new-cluster path), including its resolved availability_zone β null when helm.deploy_charts is false or once retired with manage_legacy_clickhouse_node_group = false |
The AWS infrastructure is now provisioned and the chart is deploying. Verifying that the in-cluster components (ClickHouse, the Collector, the LLM worker) came up healthy is the first step of Deploy the agent and connect to Monte Carlo.
ClickHouse and Keeper node groups
Every ClickHouse replica and every Keeper voter is a stateful pod backed by an AZ-locked EBS volume, so each one runs on its own dedicated single-AZ EKS managed node group. On the new-cluster path (with helm.deploy_charts = true) the module creates them all automatically:
- Per-AZ ClickHouse node groups β one per entry in
clickhouse_availability_zones(one node each, tainteddedicated=clickhouse:NoSchedule, instance typer6i.xlargeby default). - Per-AZ Keeper node groups β one per entry in
keeper_availability_zones(one node each, tainteddedicated=keeper:NoSchedule, instance typem6i.largeby default). - The legacy single-instance ClickHouse node group β retained by default (
manage_legacy_clickhouse_node_group = true) as a migration aid for deployments that started on the older single-instance layout.
The module wires the matching nodeSelector/tolerations into the Helm release, so the ClickHouse and Keeper pods target their node groups exclusively β no manual configuration required. The OpenTelemetry Collector, LLM worker, and cluster controllers run on the main node group. These node groups pin their AMI version by default so routine applies never roll the stateful nodes implicitly β see High availability β node AMI updates.
EBS volumes are AZ-locked. Placement is by the explicit AZ names in the topology lists β a volume cannot follow a pod to a different zone. Each listed AZ must have a private subnet (enforced at plan time), and
clickhouse_availability_zones[0]must be the AZ of any existing ClickHouse volume.
On the existing-cluster path, the module does not manage these node groups β attach tainted per-AZ node groups yourself and take the self-managed Helm route (helm.deploy_charts = false) to pass the matching scheduling values, since the module-managed release doesn't expose them. See High availability β existing-cluster deployments.
Harden network access
The ClickHouse and OpenTelemetry Collector NLBs are internal, but their allowed-source ranges (clickhouse_nlb_allowed_source_ranges / otel_collector_nlb_allowed_source_ranges) default to unrestricted. Before relying on the deployment, scope them to the networks that should reach each endpoint β see Network access in the Configuration reference.
Two NLB behaviors are worth knowing about:
- Cross-zone routing is enabled on both NLBs, so an NLB address in any AZ forwards to healthy targets in every AZ β required for the HA topology, where a zone can briefly have no local target. Standard AWS inter-AZ data-transfer charges apply (negligible at typical telemetry volumes).
- NLB subnet placement is pinned to the module's private subnets (the created ones, or
networking.existing_private_subnet_ids) rather than left to controller auto-discovery. The load balancer controller accepts at most one subnet per AZ, so keepexisting_private_subnet_idsto one subnet per AZ. On deployments upgrading from module 2.0.0, the controller re-sets each NLB's subnets in place on the first apply; if that changes the NLB's AZ set, active connections through a removed AZ are terminated β a brief, one-time blip best scheduled in a quiet window.
Next steps
Continue to Deploy the agent and connect to Monte Carlo.
Updated 1 day ago
