Skip to content

openobserve/terraform-kubernetes-openobserve

Repository files navigation

terraform-kubernetes-openobserve

Terraform module for deploying OpenObserve on Kubernetes using the official Helm chart.

OpenObserve is a cloud-native observability platform for logs, metrics, traces, dashboards, RUM, error tracking, and session replay — with Elasticsearch API compatibility.

Helm chart: openobserve/openobserve-helm-chart v0.80.3


Usage

Minimal (development / single-node)

module "openobserve" {
  source  = "openobserve/openobserve/kubernetes"
  version = "~> 1.0"

  auth = {
    root_user_email    = "admin@example.com"
    root_user_password = "ChangeMe123!"
  }

  # Single-node local mode: no PostgreSQL or NATS required
  meta_store          = "sqlite"
  cluster_coordinator = "local"
  queue_store         = "local"
  nats                = { enabled = false }
}

Access the UI:

kubectl port-forward -n openobserve svc/openobserve-router 5080:5080
# Open http://localhost:5080

Production HA (PostgreSQL + NATS + S3)

module "openobserve" {
  source  = "openobserve/openobserve/kubernetes"
  version = "~> 1.0"

  auth = {
    root_user_email    = var.root_user_email
    root_user_password = var.root_user_password
    postgres_dsn       = var.postgres_dsn
    s3_access_key      = var.s3_access_key   # omit to use IRSA
    s3_secret_key      = var.s3_secret_key
  }

  replica_count = {
    ingester     = 3
    querier      = 2
    router       = 2
    compactor    = 1
    alertmanager = 1
  }

  meta_store          = "postgres"
  cluster_coordinator = "nats"
  queue_store         = "nats"

  s3 = {
    provider    = "s3"
    region      = "us-east-1"
    bucket_name = "my-openobserve-data"
  }

  ingress = {
    enabled         = true
    class_name      = "nginx"
    host            = "openobserve.example.com"
    tls_secret_name = "openobserve-tls"
    annotations = {
      "cert-manager.io/cluster-issuer" = "letsencrypt-prod"
    }
  }

  persistence = {
    ingester     = { size = "100Gi", storage_class = "gp3" }
    querier      = { size = "100Gi", storage_class = "gp3" }
    alertmanager = { size = "10Gi",  storage_class = "gp3" }
  }

  resources = {
    ingester = {
      requests = { memory = "2Gi", cpu = "500m" }
      limits   = { memory = "8Gi", cpu = "2000m" }
    }
    querier = {
      requests = { memory = "2Gi", cpu = "500m" }
      limits   = { memory = "8Gi", cpu = "2000m" }
    }
  }

  nats  = { enabled = true }
  minio = { enabled = false }
}

Enterprise edition

module "openobserve" {
  source  = "openobserve/openobserve/kubernetes"
  version = "~> 1.0"

  image = {
    repository = "o2cr.ai/openobserve/openobserve-enterprise"
    tag        = "v0.80.2"
  }

  # ... rest of your configuration
}

Passing arbitrary Helm values

Any setting not exposed as a first-class variable can be passed through extra_values (highest precedence):

module "openobserve" {
  source  = "openobserve/openobserve/kubernetes"
  version = "~> 1.0"

  # ... required variables

  extra_values = [<<-EOT
    enterprise:
      enabled: true
    config:
      ZO_SWAGGER_ENABLED: "true"
      ZO_PROMETHEUS_ENABLED: "true"
  EOT
  ]
}

Examples

Example Description
examples/minimal Single-node SQLite deployment for development
examples/complete Production HA: PostgreSQL + NATS + S3 + Ingress + TLS

Terraform Registry

This module is published at:

registry.terraform.io/modules/openobserve/openobserve/kubernetes

Prerequisites

Requirement Version Notes
Terraform >= 1.9 optional() with defaults requires 1.3+; mock provider tests require 1.7+
hashicorp/helm provider ~> 2.16
Kubernetes cluster >= 1.25 EKS, GKE, AKS, or self-managed
Default StorageClass Required for persistent volumes
PostgreSQL >= 14 Required for meta_store = "postgres" (HA)
S3-compatible bucket Required for production data persistence

Architecture

                     ┌─────────────────────────────────────────────┐
                     │              Kubernetes Cluster              │
                     │                                              │
  Ingest / Query ───►│  router (stateless, horizontally scalable)  │
                     │      │              │                        │
                     │  ingester ◄────► querier                    │
                     │  (WAL + disk)   (disk cache)                 │
                     │      │              │                        │
                     │  compactor      alertmanager                 │
                     │      │                                        │
                     │  NATS (bundled or external)                  │
                     │      │                                        │
                     └──────┼─────────────────────────────────────-─┘
                            │
                ┌───────────┼──────────────┐
                │           │              │
           PostgreSQL       S3          Object Store
          (metadata)   (long-term data)

Upgrading the chart version

  1. Check the OpenObserve Helm chart releases for breaking changes.
  2. Update chart_version in your module call.
  3. Run terraform plan and review the diff before applying.
  4. For major chart version bumps, run terraform apply during a maintenance window.

Requirements

Requirements

Name Version
terraform >= 1.9
aws ~> 5.0
helm ~> 2.16
kubernetes ~> 2.35

Providers

Providers

Name Version
helm ~> 2.16

Inputs

Inputs

Name Description Type Default Required
affinity Pod affinity/anti-affinity rules per component.
object({
ingester = optional(any, {})
querier = optional(any, {})
router = optional(any, {})
compactor = optional(any, {})
alertmanager = optional(any, {})
})
{} no
atomic Automatically roll back the release on install/upgrade failure. bool false no
auth Authentication credentials. All values are stored in a Kubernetes Secret.
root_user_email and root_user_password are required.
Provide s3_access_key / s3_secret_key for AWS-signature S3 auth.
Provide postgres_dsn for PostgreSQL metadata store.
object({
root_user_email = string
root_user_password = string
root_user_token = optional(string, "")
s3_access_key = optional(string, "")
s3_secret_key = optional(string, "")
postgres_dsn = optional(string, "")
postgres_ro_dsn = optional(string, "")
})
n/a yes
aws_config AWS infrastructure settings. Only used when create_aws_infrastructure = true.
object({
region = optional(string, "us-east-1")
vpc_cidr = optional(string, "10.0.0.0/16")
availability_zones = optional(list(string), [])
node_instance_type = optional(string, "") # defaults to capacity recommendation
node_min_count = optional(number, 2)
node_max_count = optional(number, 10)
node_desired_count = optional(number, 0) # defaults to capacity recommendation
s3_bucket_name = optional(string, "")
s3_force_destroy = optional(bool, false)
tags = optional(map(string), {})
})
{} no
capacity Capacity planning inputs. When set, the module computes and outputs:
- recommended deployment mode (single-node vs HA)
- recommended replica counts per component
- recommended EKS instance type and node count
- S3 storage estimate
- estimated monthly cost

These are informational outputs only; set replica_count explicitly to
override the recommendations. When create_aws_infrastructure = true,
the recommendations are used as defaults for aws_config if not overridden.

Reference data (256 GB/day):
Single-node: 5 cores, ~$179/month
HA: 25 cores, ~$927/month
object({
ingestion_gb_per_day = optional(number, 0)
data_retention_days = optional(number, 30)
compression_ratio = optional(number, 0.9)
})
{} no
chart_version Version of the openobserve Helm chart. Pin this for reproducible deployments. string "0.80.3" no
cleanup_on_fail Delete newly created resources when an upgrade fails. bool false no
cluster_coordinator Cluster coordination backend. 'nats' is required for multi-node deployments. string "nats" no
config Raw ZO_* environment variable overrides merged on top of module-managed config.
Use for settings not exposed as first-class variables.
Example: { "ZO_HTTP_WORKER_NUM" = "8", "ZO_QUERY_TIMEOUT" = "300" }
map(string) {} no
create_aws_infrastructure When true, the module creates a complete AWS environment (VPC, EKS cluster,
S3 bucket, IAM role with IRSA) before deploying OpenObserve into it.
When false (default), OpenObserve is deployed into your existing cluster;
the aws_config block is ignored and no AWS resources are created.
bool false no
create_namespace Create the Kubernetes namespace if it does not exist. bool true no
data_retention_days Days to retain data before compaction removes it (ZO_COMPACT_DATA_RETENTION_DAYS). number 3650 no
extra_values List of raw YAML value strings merged last (highest precedence).
Use for helm chart sections not exposed as variables.
Example: [<<-EOT
enterprise:
enabled: true
EOT]
list(string) [] no
image Container image configuration.
Set repository to 'o2cr.ai/openobserve/openobserve-enterprise' for the enterprise edition.
Leave tag empty to use the chart's default version.
object({
repository = optional(string, "o2cr.ai/openobserve/openobserve")
tag = optional(string, "")
pull_policy = optional(string, "IfNotPresent")
})
{} no
image_pull_secrets List of Kubernetes Secret names used to pull the container image. list(string) [] no
ingress Ingress configuration. Enable to expose OpenObserve externally.
Requires an Ingress controller (e.g. nginx-ingress) in the cluster.
Set tls_secret_name to enable HTTPS with cert-manager.
object({
enabled = optional(bool, false)
class_name = optional(string, "nginx")
host = optional(string, "")
annotations = optional(map(string), {})
tls_secret_name = optional(string, "")
})
{} no
meta_store Metadata storage backend. Use 'postgres' for all HA deployments. string "postgres" no
minio MinIO dependency bundled with the chart. Disable when using AWS S3 or an external MinIO instance.
object({
enabled = optional(bool, false)
})
{} no
namespace Kubernetes namespace to deploy OpenObserve into. string "openobserve" no
nats NATS dependency bundled with the chart. Disable when using an external NATS cluster.
object({
enabled = optional(bool, true)
})
{} no
node_selector Node selector labels per component. Keys follow the per-component pattern used by the chart (ingester, querier, router, compactor, alertmanager).
object({
ingester = optional(map(string), {})
querier = optional(map(string), {})
router = optional(map(string), {})
compactor = optional(map(string), {})
alertmanager = optional(map(string), {})
})
{} no
persistence Persistent volume configuration per component. Storage class defaults to the cluster default when empty.
object({
ingester = optional(object({
enabled = optional(bool, true)
size = optional(string, "100Gi")
storage_class = optional(string, "")
access_modes = optional(list(string), ["ReadWriteOnce"])
}), {})
querier = optional(object({
enabled = optional(bool, true)
size = optional(string, "100Gi")
storage_class = optional(string, "")
access_modes = optional(list(string), ["ReadWriteOnce"])
}), {})
alertmanager = optional(object({
enabled = optional(bool, true)
size = optional(string, "10Gi")
storage_class = optional(string, "")
access_modes = optional(list(string), ["ReadWriteOnce"])
}), {})
})
{} no
queue_store Distributed queue backend. 'nats' is required for multi-node deployments. string "nats" no
release_name Name of the Helm release. string "openobserve" no
replica_count Number of replicas per component. Increase querier/ingester for HA; router is stateless and scales horizontally.
object({
ingester = optional(number, 1)
querier = optional(number, 1)
router = optional(number, 1)
compactor = optional(number, 1)
alertmanager = optional(number, 1)
})
{} no
resources CPU/memory resource requests and limits per component.
Example:
resources = {
ingester = { requests = { memory = "2Gi", cpu = "500m" }, limits = { memory = "8Gi", cpu = "2000m" } }
querier = { requests = { memory = "2Gi", cpu = "500m" }, limits = { memory = "8Gi" } }
}
any {} no
s3 S3-compatible object storage configuration.
OpenObserve uses S3 for long-term data persistence.
Set server_url to use MinIO or other S3-compatible providers.
object({
provider = optional(string, "s3")
region = optional(string, "us-east-1")
bucket_name = optional(string, "")
server_url = optional(string, "")
bucket_prefix = optional(string, "")
})
{} no
service Kubernetes Service configuration for the router component.
object({
type = optional(string, "ClusterIP")
http_port = optional(number, 5080)
grpc_port = optional(number, 5081)
})
{} no
timeout Timeout in seconds for Helm install/upgrade operations. number 600 no
tolerations Pod tolerations per component. Each element is a Kubernetes toleration object.
object({
ingester = optional(list(any), [])
querier = optional(list(any), [])
router = optional(list(any), [])
compactor = optional(list(any), [])
alertmanager = optional(list(any), [])
})
{} no
wait Wait for all pods and services to be ready before marking the release as successful. bool true no

Outputs

Outputs

Name Description
app_version Application version reported by the Helm chart metadata.
aws_infrastructure AWS resource details created by the module. Null when create_aws_infrastructure = false.
capacity_recommendations Capacity planning recommendations derived from your ingestion_gb_per_day input.
Use these to right-size replicas, EKS nodes, and plan AWS spend before applying.
Set capacity.ingestion_gb_per_day to enable.
chart_version Version of the deployed Helm chart.
grpc_endpoint In-cluster gRPC endpoint used by OpenTelemetry exporters.
http_endpoint In-cluster HTTP endpoint for the OpenObserve UI and ingestion API.
ingress_host Ingress hostname, or empty string when ingress is disabled.
namespace Kubernetes namespace where OpenObserve is deployed.
release_name Name of the deployed Helm release.
service_name Kubernetes Service name for the OpenObserve router (entry point for all traffic).
status Current Helm release status (e.g. deployed, failed).

Contributing

See CONTRIBUTING.md.

License

Apache 2.0 — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages