Setting up AWS OpenSearch Service with Terraform

Learn how to provision and manage AWS OpenSearch domains using Terraform, including configuration, security, and monitoring best practices

Setting up AWS OpenSearch Service with Terraform

AWS OpenSearch Service is a managed service that makes it easy to deploy, operate, and scale OpenSearch clusters. This guide demonstrates how to set up and manage OpenSearch using Terraform.

Video Tutorial

Learn more about managing AWS OpenSearch with Terraform in this comprehensive video tutorial:

Prerequisites

  • AWS CLI configured with appropriate permissions
  • Terraform installed (version 1.0.0 or later)
  • Basic understanding of OpenSearch/Elasticsearch
  • VPC with private subnets

Project Structure

terraform-opensearch/
├── main.tf
├── variables.tf
├── outputs.tf
├── modules/
│   └── opensearch/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
└── config/
    └── opensearch.yml

OpenSearch Configuration

Create modules/opensearch/main.tf:

# OpenSearch Domain
resource "aws_opensearch_domain" "main" {
  domain_name    = "${var.project_name}-domain"
  engine_version = "OpenSearch_2.5"

  cluster_config {
    instance_type            = var.instance_type
    instance_count          = var.instance_count
    zone_awareness_enabled  = true
    
    zone_awareness_config {
      availability_zone_count = 3
    }

    dedicated_master_enabled = true
    dedicated_master_type   = var.master_instance_type
    dedicated_master_count  = 3

    warm_enabled = true
    warm_type    = var.warm_instance_type
    warm_count   = 2

    cold_storage_options {
      enabled = true
    }
  }

  ebs_options {
    ebs_enabled = true
    volume_type = "gp3"
    volume_size = var.volume_size
    iops        = 3000
  }

  vpc_options {
    subnet_ids         = var.subnet_ids
    security_group_ids = [aws_security_group.opensearch.id]
  }

  encrypt_at_rest {
    enabled    = true
    kms_key_id = aws_kms_key.opensearch.arn
  }

  node_to_node_encryption {
    enabled = true
  }

  domain_endpoint_options {
    enforce_https       = true
    tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
    
    custom_endpoint_enabled         = true
    custom_endpoint                = "search.${var.domain_name}"
    custom_endpoint_certificate_arn = aws_acm_certificate.opensearch.arn
  }

  advanced_security_options {
    enabled                        = true
    internal_user_database_enabled = true
    
    master_user_options {
      master_user_name     = var.master_user_name
      master_user_password = var.master_user_password
    }
  }

  auto_tune_options {
    desired_state = "ENABLED"
    maintenance_schedule {
      start_at = timeadd(timestamp(), "24h")
      duration {
        value = 2
        unit  = "HOURS"
      }
      cron_expression_for_recurrence = "cron(0 0 ? * 1 *)"
    }
  }

  log_publishing_options {
    cloudwatch_log_group_arn = aws_cloudwatch_log_group.opensearch_logs.arn
    log_type                = "INDEX_SLOW_LOGS"
  }

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-domain"
    }
  )
}

# Security Group
resource "aws_security_group" "opensearch" {
  name        = "${var.project_name}-opensearch"
  description = "Security group for OpenSearch domain"
  vpc_id      = var.vpc_id

  ingress {
    description = "HTTPS from VPC"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = var.allowed_cidr_blocks
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-opensearch"
    }
  )
}

# KMS Key
resource "aws_kms_key" "opensearch" {
  description             = "KMS key for OpenSearch domain encryption"
  deletion_window_in_days = 7
  enable_key_rotation     = true

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "Enable IAM User Permissions"
        Effect = "Allow"
        Principal = {
          AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
        }
        Action   = "kms:*"
        Resource = "*"
      }
    ]
  })

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-opensearch"
    }
  )
}

# CloudWatch Log Group
resource "aws_cloudwatch_log_group" "opensearch_logs" {
  name              = "/aws/opensearch/${var.project_name}"
  retention_in_days = 30

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-logs"
    }
  )
}

# Service-Linked Role
resource "aws_iam_service_linked_role" "opensearch" {
  aws_service_name = "opensearchservice.amazonaws.com"
}

Access Policies

  1. Domain Access Policy
resource "aws_opensearch_domain_policy" "main" {
  domain_name = aws_opensearch_domain.main.domain_name

  access_policies = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          AWS = [
            data.aws_caller_identity.current.account_id
          ]
        }
        Action = [
          "es:ESHttp*"
        ]
        Resource = "${aws_opensearch_domain.main.arn}/*"
        Condition = {
          IpAddress = {
            "aws:SourceIp" = var.allowed_ip_ranges
          }
        }
      }
    ]
  })
}
  1. Fine-Grained Access Control
resource "aws_opensearch_domain_saml_options" "main" {
  domain_name = aws_opensearch_domain.main.domain_name

  saml_options {
    enabled = true
    idp {
      entity_id        = var.idp_entity_id
      metadata_content = file("${path.module}/saml-metadata.xml")
    }
    master_backend_role = "admin"
    master_user_name    = var.master_user_name
    roles_key          = "Role"
    session_timeout_minutes = 60
  }
}

Monitoring and Alerts

  1. CloudWatch Alarms
resource "aws_cloudwatch_metric_alarm" "cluster_status" {
  alarm_name          = "${var.project_name}-cluster-status"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = "1"
  metric_name        = "ClusterStatus.red"
  namespace          = "AWS/ES"
  period             = "60"
  statistic          = "Maximum"
  threshold          = "0"
  alarm_description  = "Cluster status is red"
  alarm_actions      = [aws_sns_topic.alerts.arn]

  dimensions = {
    DomainName = aws_opensearch_domain.main.domain_name
  }
}

resource "aws_cloudwatch_metric_alarm" "free_storage" {
  alarm_name          = "${var.project_name}-free-storage"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = "1"
  metric_name        = "FreeStorageSpace"
  namespace          = "AWS/ES"
  period             = "300"
  statistic          = "Minimum"
  threshold          = "20000"
  alarm_description  = "Free storage space is low"
  alarm_actions      = [aws_sns_topic.alerts.arn]

  dimensions = {
    DomainName = aws_opensearch_domain.main.domain_name
  }
}

Backup and Recovery

  1. Automated Snapshots
resource "aws_opensearch_domain" "with_backup" {
  # ... other configuration ...

  snapshot_options {
    automated_snapshot_start_hour = 0
  }
}
  1. Manual Snapshots
resource "aws_s3_bucket" "snapshots" {
  bucket = "${var.project_name}-snapshots"

  tags = merge(
    var.tags,
    {
      Name = "${var.project_name}-snapshots"
    }
  )
}

resource "aws_iam_role" "snapshot_role" {
  name = "${var.project_name}-snapshot-role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "opensearchservice.amazonaws.com"
        }
      }
    ]
  })
}

Integration Examples

  1. Logstash Configuration
output {
  opensearch {
    hosts => ["${aws_opensearch_domain.main.endpoint}:443"]
    ssl => true
    ssl_certificate_verification => true
    user => var.master_user_name
    password => var.master_user_password
    index => "logs-%{+YYYY.MM.dd}"
  }
}
  1. Fluentd Configuration
<match **>
  @type opensearch
  host "#{aws_opensearch_domain.main.endpoint}"
  port 443
  scheme https
  ssl_verify true
  user "#{var.master_user_name}"
  password "#{var.master_user_password}"
  index_name fluentd-${tag}-%Y%m%d
  include_timestamp true
</match>

Best Practices

  1. Performance Optimization

    • Choose appropriate instance types
    • Configure proper shard allocation
    • Use dedicated master nodes
    • Implement index lifecycle management
  2. High Availability

    • Enable zone awareness
    • Use dedicated master nodes
    • Configure proper replication
    • Regular monitoring
  3. Security

    • Enable encryption
    • Implement fine-grained access control
    • Use VPC endpoints
    • Regular security updates
  4. Cost Optimization

    • Use UltraWarm for old indices
    • Implement index lifecycle policies
    • Monitor usage patterns
    • Clean up old indices

Advanced Features

  1. Index Templates
PUT _template/logs
{
  "index_patterns": ["logs-*"],
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 1,
    "index.lifecycle.name": "logs_policy",
    "index.routing.allocation.require.temperature": "hot"
  },
  "mappings": {
    "properties": {
      "@timestamp": {
        "type": "date"
      },
      "message": {
        "type": "text"
      },
      "level": {
        "type": "keyword"
      }
    }
  }
}
  1. Index Lifecycle Management
PUT _ilm/policy/logs_policy
{
  "policy": {
    "phases": {
      "hot": {
        "min_age": "0ms",
        "actions": {
          "rollover": {
            "max_size": "50GB",
            "max_age": "7d"
          }
        }
      },
      "warm": {
        "min_age": "30d",
        "actions": {
          "allocate": {
            "require": {
              "temperature": "warm"
            }
          }
        }
      },
      "cold": {
        "min_age": "60d",
        "actions": {
          "allocate": {
            "require": {
              "temperature": "cold"
            }
          }
        }
      },
      "delete": {
        "min_age": "90d",
        "actions": {
          "delete": {}
        }
      }
    }
  }
}

Conclusion

You’ve learned how to set up and manage AWS OpenSearch using Terraform. This setup provides:

  • OpenSearch domain management
  • Security and encryption
  • Monitoring and logging
  • Backup and recovery

Remember to:

  • Monitor cluster health
  • Implement security best practices
  • Optimize performance
  • Maintain backups