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
- 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
}
}
}
]
})
}
- 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
- 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
- Automated Snapshots
resource "aws_opensearch_domain" "with_backup" {
# ... other configuration ...
snapshot_options {
automated_snapshot_start_hour = 0
}
}
- 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
- 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}"
}
}
- 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
-
Performance Optimization
- Choose appropriate instance types
- Configure proper shard allocation
- Use dedicated master nodes
- Implement index lifecycle management
-
High Availability
- Enable zone awareness
- Use dedicated master nodes
- Configure proper replication
- Regular monitoring
-
Security
- Enable encryption
- Implement fine-grained access control
- Use VPC endpoints
- Regular security updates
-
Cost Optimization
- Use UltraWarm for old indices
- Implement index lifecycle policies
- Monitor usage patterns
- Clean up old indices
Advanced Features
- 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"
}
}
}
}
- 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