Add support for ElasticSearch/OpenSearch as a storage (#30)

- Refactor database variables to support multiple storages more easily.
diff --git a/ansible/roles/skywalking/templates/skywalking-oap.env.j2 b/ansible/roles/skywalking/templates/skywalking-oap.env.j2
index 981d0d4..77c9ead 100644
--- a/ansible/roles/skywalking/templates/skywalking-oap.env.j2
+++ b/ansible/roles/skywalking/templates/skywalking-oap.env.j2
@@ -21,13 +21,16 @@
 {% set storage = database['type'] %}
 
 {% if storage and (storage | length) %}
-SW_STORAGE={{ storage | regex_replace('^rds-', '')}}
+SW_STORAGE={{ storage | regex_replace('^rds_', '')}}
 {% endif %}
 
 {% if "postgresql" in storage %}
 SW_JDBC_URL=jdbc:postgresql://{{ database["host"] }}:{{ database["port"] }}/{{ database["name"] }}
 SW_DATA_SOURCE_USER={{ database['user'] }}
 SW_DATA_SOURCE_PASSWORD={{ database['password'] }}
+{% elif "elasticsearch" in storage %}
+SW_STORAGE_ES_CLUSTER_NODES={{ database["host"] }}
+SW_STORAGE_ES_HTTP_PROTOCOL=https
 {% endif %}
 
 {% for key, value in skywalking_oap_environment.items() %}
diff --git a/ansible/template/group_vars/skywalking_oap.yaml.tftpl b/ansible/template/group_vars/skywalking_oap.yaml.tftpl
new file mode 100644
index 0000000..63c15bf
--- /dev/null
+++ b/ansible/template/group_vars/skywalking_oap.yaml.tftpl
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+database:
+  type: ${database_type}
+  host: ${database_host}
+  port: ${database_port}
+  name: ${database_name}
+  user: ${database_user}
+  password: ${database_password}
+
diff --git a/ansible/template/inventory.yaml.tftpl b/ansible/template/inventory.yaml.tftpl
index a979a64..b8bbb40 100644
--- a/ansible/template/inventory.yaml.tftpl
+++ b/ansible/template/inventory.yaml.tftpl
@@ -34,14 +34,6 @@
 %{ for oap in oap_instances ~}
     ${oap.private_ip}:
 %{ endfor ~}
-  vars:
-    database:
-      type: ${database_type}
-      host: ${database_host}
-      port: ${database_port}
-      name: ${database_name}
-      user: ${database_user}
-      password: ${database_password}
 
 skywalking_ui:
   hosts:
diff --git a/aws/alb-main.tf b/aws/alb-main.tf
index 83d1dcf..1e85c16 100644
--- a/aws/alb-main.tf
+++ b/aws/alb-main.tf
@@ -57,6 +57,17 @@
           port      = 8080
         }
       ]
+      health_check = {
+        enabled             = true
+        interval            = 30
+        path                = "/internal/l7check"
+        port                = "traffic-port"
+        healthy_threshold   = 3
+        unhealthy_threshold = 3
+        timeout             = 6
+        protocol            = "HTTP"
+        matcher             = "200"
+      }
     }
   ]
 
diff --git a/aws/configurations.md b/aws/configurations.md
index 431266f..8907397 100644
--- a/aws/configurations.md
+++ b/aws/configurations.md
@@ -24,12 +24,18 @@
 
 | Name | Type |
 |------|------|
+| [aws_elasticsearch_domain.elasticsearch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/elasticsearch_domain) | resource |
 | [aws_security_group.alb-skywalking-ui](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
 | [aws_security_group.allow_apps](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [aws_security_group.elasticsearch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
 | [aws_security_group.public-egress-access](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource |
+| [local_file.elasticsearch_vars](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
+| [local_file.h2_vars](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
 | [local_file.inventories](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
+| [local_file.rds_postgresql_vars](https://registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file) | resource |
 | [random_password.rds_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource |
 | [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source |
+| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
 
 ## Inputs
 
@@ -43,12 +49,6 @@
 | <a name="input_cluster_name"></a> [cluster\_name](#input\_cluster\_name) | Name of the cluster | `string` | `"skywalking-cluster"` | no |
 | <a name="input_create_lb"></a> [create\_lb](#input\_create\_lb) | Create load balancer for SkyWalking UI | `bool` | `true` | no |
 | <a name="input_database_subnets"></a> [database\_subnets](#input\_database\_subnets) | CIDR used for database subnets | `set(string)` | <pre>[<br>  "11.0.104.0/24",<br>  "11.0.105.0/24",<br>  "11.0.106.0/24"<br>]</pre> | no |
-| <a name="input_db_instance_class"></a> [db\_instance\_class](#input\_db\_instance\_class) | Instance class for the database | `string` | `"db.t3.medium"` | no |
-| <a name="input_db_max_storage_size"></a> [db\_max\_storage\_size](#input\_db\_max\_storage\_size) | Maximum storage size for the database, in GB | `number` | `100` | no |
-| <a name="input_db_name"></a> [db\_name](#input\_db\_name) | Name of the database | `string` | `"skywalking"` | no |
-| <a name="input_db_password"></a> [db\_password](#input\_db\_password) | Password for the database, if not set, a random password will be generated. | `string` | `null` | no |
-| <a name="input_db_storage_size"></a> [db\_storage\_size](#input\_db\_storage\_size) | Storage size for the database, in GB | `number` | `5` | no |
-| <a name="input_db_username"></a> [db\_username](#input\_db\_username) | Username for the database | `string` | `"skywalking"` | no |
 | <a name="input_extra_tags"></a> [extra\_tags](#input\_extra\_tags) | Additional tags to be added to all resources | `map(string)` | `{}` | no |
 | <a name="input_oap_instance_count"></a> [oap\_instance\_count](#input\_oap\_instance\_count) | Number of OAP instances, if you want to use H2 storage, you must set it to 1. | `number` | `1` | no |
 | <a name="input_oap_instance_type"></a> [oap\_instance\_type](#input\_oap\_instance\_type) | CPU, memory, storage and networking capacity for OAP instances | `string` | `"c5.xlarge"` | no |
@@ -57,7 +57,7 @@
 | <a name="input_public_subnets"></a> [public\_subnets](#input\_public\_subnets) | CIDR used for public subnets | `set(string)` | <pre>[<br>  "11.0.101.0/24",<br>  "11.0.102.0/24",<br>  "11.0.103.0/24"<br>]</pre> | no |
 | <a name="input_region"></a> [region](#input\_region) | Physical location for clustered data centers. | `string` | `"us-east-1"` | no |
 | <a name="input_secret_key"></a> [secret\_key](#input\_secret\_key) | Secret key of the AWS account, if you have configured AWS CLI, you can leave it empty. | `string` | `""` | no |
-| <a name="input_storage"></a> [storage](#input\_storage) | Storage type for SkyWalking OAP, can be 'h2', or 'rds-postgresql' | `string` | `"rds-postgresql"` | no |
+| <a name="input_storage"></a> [storage](#input\_storage) | Storage configuration for SkyWalking OAP | <pre>object({<br>    h2 = optional(object({}))<br>    rds_postgresql = optional(object({<br>      db_storage_size_gb     = optional(number)<br>      db_max_storage_size_gb = optional(number)<br>      db_instance_class      = optional(string)<br>      db_name                = optional(string)<br>      db_username            = optional(string)<br>      db_password            = optional(string)<br>    }))<br>    elasticsearch = optional(object({<br>      domain_name                = optional(string)<br>      version                    = optional(string)<br>      instance_type              = optional(string)<br>      instance_count             = optional(number)<br>      additional_security_groups = optional(list(string))<br>      zone_awareness_enabled     = optional(bool)<br>      availability_zone_count    = optional(number)<br>      ebs_enabled                = optional(bool)<br>    }))<br>  })</pre> | <pre>{<br>  "h2": {}<br>}</pre> | no |
 | <a name="input_ui_instance_count"></a> [ui\_instance\_count](#input\_ui\_instance\_count) | Number of UI instances | `number` | `1` | no |
 | <a name="input_ui_instance_type"></a> [ui\_instance\_type](#input\_ui\_instance\_type) | CPU, memory, storage and networking capacity for UI instances | `string` | `"t2.medium"` | no |
 
@@ -72,6 +72,7 @@
 | <a name="output_database_password"></a> [database\_password](#output\_database\_password) | The database password |
 | <a name="output_database_port"></a> [database\_port](#output\_database\_port) | The database port |
 | <a name="output_database_username"></a> [database\_username](#output\_database\_username) | The database username |
+| <a name="output_elasticsearch_endpoint"></a> [elasticsearch\_endpoint](#output\_elasticsearch\_endpoint) | The elasticsearch endpoint |
 | <a name="output_oap_ips"></a> [oap\_ips](#output\_oap\_ips) | The private IPs of the OAP instances |
 | <a name="output_ssh_user_key_file"></a> [ssh\_user\_key\_file](#output\_ssh\_user\_key\_file) | The SSH private key file to use to connect to the bastion host |
 | <a name="output_ui_ips"></a> [ui\_ips](#output\_ui\_ips) | The IPs of the SkyWalking UI instances |
diff --git a/aws/ec2-main.tf b/aws/ec2-main.tf
index 50d27c6..288f86b 100644
--- a/aws/ec2-main.tf
+++ b/aws/ec2-main.tf
@@ -34,15 +34,9 @@
   filename        = "${path.module}/../ansible/inventory/skywalking.yaml"
   file_permission = "0600"
   content = templatefile("${path.module}/../ansible/template/inventory.yaml.tftpl", {
-    bastion           = module.skywalking.bastion_instances[0]
-    oap_instances     = module.skywalking.oap_instances
-    ui_instances      = module.skywalking.ui_instances
-    private_key_file  = module.skywalking.ssh_user_key_file
-    database_type     = var.storage
-    database_host     = var.storage == "rds-postgresql" ? module.rds[0].db_instance_address : ""
-    database_port     = var.storage == "rds-postgresql" ? module.rds[0].db_instance_port : ""
-    database_user     = var.storage == "rds-postgresql" ? module.rds[0].db_instance_username : ""
-    database_name     = var.storage == "rds-postgresql" ? module.rds[0].db_instance_name : ""
-    database_password = var.storage == "rds-postgresql" ? local.database_password : ""
+    bastion          = module.skywalking.bastion_instances[0]
+    oap_instances    = module.skywalking.oap_instances
+    ui_instances     = module.skywalking.ui_instances
+    private_key_file = module.skywalking.ssh_user_key_file
   })
 }
diff --git a/aws/elasticsearch-main.tf b/aws/elasticsearch-main.tf
new file mode 100644
index 0000000..e781916
--- /dev/null
+++ b/aws/elasticsearch-main.tf
@@ -0,0 +1,99 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+locals {
+  elasticsearch_domain_name                = coalesce(lookup(local.storage_config, "domain_name"), var.cluster_name)
+  elasticsearch_version                    = coalesce(lookup(local.storage_config, "version"), "7.10")
+  elasticsearch_instance_type              = coalesce(lookup(local.storage_config, "instance_type"), "m3.medium.elasticsearch")
+  elasticsearch_instance_count             = coalesce(lookup(local.storage_config, "instance_count"), 2)
+  elasticsearch_additional_security_groups = coalesce(lookup(local.storage_config, "additional_security_groups"), [])
+  elasticsearch_zone_awareness_enabled     = coalesce(lookup(local.storage_config, "zone_awareness_enabled"), false)
+  elasticsearch_availability_zone_count    = coalesce(lookup(local.storage_config, "availability_zone_count"), 2)
+  elasticsearch_ebs_enabled                = coalesce(lookup(local.storage_config, "ebs_enabled"), false)
+}
+
+data "aws_caller_identity" "current" {}
+
+resource "aws_elasticsearch_domain" "elasticsearch" {
+  count = local.storage_name == "elasticsearch" ? 1 : 0
+
+  domain_name           = local.elasticsearch_domain_name
+  elasticsearch_version = local.elasticsearch_version
+
+  cluster_config {
+    instance_type          = local.elasticsearch_instance_type
+    instance_count         = local.elasticsearch_instance_count
+    zone_awareness_enabled = local.elasticsearch_zone_awareness_enabled
+    zone_awareness_config {
+      availability_zone_count = local.elasticsearch_availability_zone_count
+    }
+  }
+
+  vpc_options {
+    subnet_ids = slice(module.vpc.private_subnets, 0, local.elasticsearch_zone_awareness_enabled ? 2 : 1)
+
+    security_group_ids = [aws_security_group.elasticsearch.id]
+  }
+
+  ebs_options {
+    ebs_enabled = local.elasticsearch_ebs_enabled
+  }
+
+  access_policies = <<CONFIG
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Action": "es:*",
+      "Principal": "*",
+      "Effect": "Allow",
+      "Resource": "arn:aws:es:${var.region}:${data.aws_caller_identity.current.account_id}:domain/${local.elasticsearch_domain_name}/*"
+    }
+  ]
+}
+CONFIG
+
+  tags = var.extra_tags
+}
+
+resource "aws_security_group" "elasticsearch" {
+  name        = "${module.vpc.vpc_id}-elasticsearch-${local.elasticsearch_domain_name}"
+  description = "Security group to allow requests to ElasticSearch"
+  vpc_id      = module.vpc.vpc_id
+
+  ingress {
+    from_port       = 443
+    to_port         = 443
+    protocol        = "tcp"
+    security_groups = module.skywalking.oap_security_groups
+  }
+}
+
+resource "local_file" "elasticsearch_vars" {
+  count = local.storage_name == "elasticsearch" ? 1 : 0
+
+  filename        = "${path.module}/../ansible/inventory/group_vars/skywalking_oap.yaml"
+  file_permission = "0600"
+  content = templatefile("${path.module}/../ansible/template/group_vars/skywalking_oap.yaml.tftpl", {
+    database_type     = local.storage_name
+    database_host     = local.storage_name == "elasticsearch" ? aws_elasticsearch_domain.elasticsearch[0].endpoint : ""
+    database_port     = ""
+    database_user     = ""
+    database_name     = ""
+    database_password = ""
+  })
+}
diff --git a/aws/elasticsearch-output.tf b/aws/elasticsearch-output.tf
new file mode 100644
index 0000000..25c4284
--- /dev/null
+++ b/aws/elasticsearch-output.tf
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+output "elasticsearch_endpoint" {
+  value       = local.storage_name == "elasticsearch" ? aws_elasticsearch_domain.elasticsearch[0].endpoint : ""
+  description = "The elasticsearch endpoint"
+}
diff --git a/aws/h2-main.tf b/aws/h2-main.tf
new file mode 100644
index 0000000..9b3e767
--- /dev/null
+++ b/aws/h2-main.tf
@@ -0,0 +1,31 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+resource "local_file" "h2_vars" {
+  count = local.storage_name == "h2" ? 1 : 0
+
+  filename        = "${path.module}/../ansible/inventory/group_vars/skywalking_oap.yaml"
+  file_permission = "0600"
+  content = templatefile("${path.module}/../ansible/template/group_vars/skywalking_oap.yaml.tftpl", {
+    database_type     = local.storage_name
+    database_host     = ""
+    database_port     = ""
+    database_user     = ""
+    database_name     = ""
+    database_password = ""
+  })
+}
diff --git a/aws/modules/skywalking/README.md b/aws/modules/skywalking/README.md
index 35abf8d..5eea8b8 100644
--- a/aws/modules/skywalking/README.md
+++ b/aws/modules/skywalking/README.md
@@ -46,7 +46,7 @@
 | <a name="input_oap_instance_subnet_id"></a> [oap\_instance\_subnet\_id](#input\_oap\_instance\_subnet\_id) | Subnet ID for OAP instances | `string` | n/a | yes |
 | <a name="input_oap_instance_type"></a> [oap\_instance\_type](#input\_oap\_instance\_type) | CPU, memory, storage and networking capacity for OAP instances | `string` | `"c5.xlarge"` | no |
 | <a name="input_public_key_path"></a> [public\_key\_path](#input\_public\_key\_path) | Path to store the key file for SSH access to the instances. | `string` | `"~/.ssh"` | no |
-| <a name="input_storage"></a> [storage](#input\_storage) | Storage type for SkyWalking OAP, can be `h2`, or `rds-postgresql` | `string` | `"rds-postgresql"` | no |
+| <a name="input_storage"></a> [storage](#input\_storage) | Storage type for SkyWalking OAP, can be `h2`, `elasticsearch` or `rds-postgresql` | `string` | `"rds-postgresql"` | no |
 | <a name="input_ui_instance_ami_id"></a> [ui\_instance\_ami\_id](#input\_ui\_instance\_ami\_id) | AMI ID for UI instances, if not set, a suitable AMI ID will be selected automatically. | `string` | `""` | no |
 | <a name="input_ui_instance_count"></a> [ui\_instance\_count](#input\_ui\_instance\_count) | Number of UI instances | `number` | `1` | no |
 | <a name="input_ui_instance_security_group_ids"></a> [ui\_instance\_security\_group\_ids](#input\_ui\_instance\_security\_group\_ids) | Additional security groups for UI instances | `list(string)` | `[]` | no |
diff --git a/aws/modules/skywalking/variables.tf b/aws/modules/skywalking/variables.tf
index 6e8818e..ef2769f 100644
--- a/aws/modules/skywalking/variables.tf
+++ b/aws/modules/skywalking/variables.tf
@@ -120,12 +120,12 @@
 ## Storage
 variable "storage" {
   type        = string
-  description = "Storage type for SkyWalking OAP, can be `h2`, or `rds-postgresql`"
+  description = "Storage type for SkyWalking OAP, can be `h2`, `elasticsearch` or `rds-postgresql`"
   default     = "rds-postgresql"
 
   validation {
-    condition     = contains(["h2", "rds-postgresql"], var.storage)
-    error_message = "Allowed values for storage are \"h2\", \"rds-postgresql\"."
+    condition     = contains(["h2", "elasticsearch", "rds_postgresql"], var.storage)
+    error_message = "Allowed values for storage are \"h2\", \"rds_postgresql\"."
   }
 }
 
diff --git a/aws/rds-postgresql-main.tf b/aws/rds-postgresql-main.tf
index fd685c9..a4bfe46 100644
--- a/aws/rds-postgresql-main.tf
+++ b/aws/rds-postgresql-main.tf
@@ -21,22 +21,27 @@
 }
 
 locals {
-  database_password = var.db_password != null ? var.db_password : random_password.rds_password.result
+  database_instance_class   = coalesce(lookup(local.storage_config, "db_instance_class", "db.t3.small"))
+  database_storage_size     = coalesce(lookup(local.storage_config, "db_storage_size_gb", 20))
+  database_max_storage_size = coalesce(lookup(local.storage_config, "db_max_storage_size_gb", 100))
+  database_name             = coalesce(lookup(local.storage_config, "db_name", "skywalking"))
+  database_username         = coalesce(lookup(local.storage_config, "db_username", "skywalking"))
+  database_password         = coalesce(lookup(local.storage_config, "db_password", random_password.rds_password.result))
 }
 
 module "rds" {
   source  = "terraform-aws-modules/rds/aws"
   version = "~> 5.0"
 
-  count = var.storage == "rds-postgresql" ? 1 : 0
+  count = local.storage_name == "rds_postgresql" ? 1 : 0
 
   identifier = var.cluster_name
 
-  allocated_storage     = var.db_storage_size
-  max_allocated_storage = var.db_max_storage_size
+  allocated_storage     = local.database_storage_size
+  max_allocated_storage = local.database_max_storage_size
 
-  db_name                = var.db_name
-  username               = var.db_username
+  db_name                = local.database_name
+  username               = local.database_username
   password               = local.database_password
   create_random_password = false
   port                   = "5432"
@@ -62,7 +67,7 @@
   engine_version         = "15"
   family                 = "postgres15"
   major_engine_version   = "15.3"
-  instance_class         = var.db_instance_class
+  instance_class         = local.database_instance_class
   create_db_option_group = "false"
 
   parameters = [
@@ -92,3 +97,18 @@
     cidr_blocks = ["0.0.0.0/0"]
   }
 }
+
+resource "local_file" "rds_postgresql_vars" {
+  count = local.storage_name == "rds_postgresql" ? 1 : 0
+
+  filename        = "${path.module}/../ansible/inventory/group_vars/skywalking_oap.yaml"
+  file_permission = "0600"
+  content = templatefile("${path.module}/../ansible/template/group_vars/skywalking_oap.yaml.tftpl", {
+    database_type     = local.storage_name
+    database_host     = local.storage_name == "rds_postgresql" ? module.rds[0].db_instance_address : ""
+    database_port     = local.storage_name == "rds_postgresql" ? module.rds[0].db_instance_port : ""
+    database_user     = local.storage_name == "rds_postgresql" ? module.rds[0].db_instance_username : ""
+    database_name     = local.storage_name == "rds_postgresql" ? module.rds[0].db_instance_name : ""
+    database_password = local.storage_name == "rds_postgresql" ? local.database_password : ""
+  })
+}
diff --git a/aws/skywalking-main.tf b/aws/skywalking-main.tf
index 6ca40d1..64179d3 100644
--- a/aws/skywalking-main.tf
+++ b/aws/skywalking-main.tf
@@ -15,11 +15,19 @@
 # specific language governing permissions and limitations
 # under the License.
 
+locals {
+  storage = {
+    for storage, config in var.storage : storage => config if config != null
+  }
+  storage_name   = keys(local.storage)[0]
+  storage_config = values(local.storage)[0]
+}
+
 module "skywalking" {
   source = "./modules/skywalking"
 
   cluster_name = var.cluster_name
-  storage      = var.storage
+  storage      = local.storage_name
 
   oap_instance_count     = var.oap_instance_count
   oap_instance_type      = var.oap_instance_type
diff --git a/aws/variables.tf b/aws/variables.tf
index f78b241..a0ae681 100644
--- a/aws/variables.tf
+++ b/aws/variables.tf
@@ -118,52 +118,40 @@
 
 ## Storage
 variable "storage" {
-  type        = string
-  description = "Storage type for SkyWalking OAP, can be 'h2', or 'rds-postgresql'"
-  default     = "rds-postgresql"
+  description = "Storage configuration for SkyWalking OAP"
+
+  type = object({
+    h2 = optional(object({}))
+    rds_postgresql = optional(object({
+      db_storage_size_gb     = optional(number)
+      db_max_storage_size_gb = optional(number)
+      db_instance_class      = optional(string)
+      db_name                = optional(string)
+      db_username            = optional(string)
+      db_password            = optional(string)
+    }))
+    elasticsearch = optional(object({
+      domain_name                = optional(string)
+      version                    = optional(string)
+      instance_type              = optional(string)
+      instance_count             = optional(number)
+      additional_security_groups = optional(list(string))
+      zone_awareness_enabled     = optional(bool)
+      availability_zone_count    = optional(number)
+      ebs_enabled                = optional(bool)
+    }))
+  })
+
+  default = {
+    h2 = {}
+  }
 
   validation {
-    condition     = contains(["h2", "rds-postgresql"], var.storage)
-    error_message = "Allowed values for storage are \"h2\", \"rds-postgresql\"."
+    condition     = length([for storage, value in var.storage : storage if value != null]) == 1
+    error_message = "Please only set one storage type."
   }
 }
 
-variable "db_name" {
-  type        = string
-  description = "Name of the database"
-  default     = "skywalking"
-}
-
-variable "db_username" {
-  type        = string
-  description = "Username for the database"
-  default     = "skywalking"
-}
-
-variable "db_password" {
-  type        = string
-  description = "Password for the database, if not set, a random password will be generated."
-  default     = null
-}
-
-variable "db_storage_size" {
-  type        = number
-  description = "Storage size for the database, in GB"
-  default     = 5
-}
-
-variable "db_max_storage_size" {
-  type        = number
-  description = "Maximum storage size for the database, in GB"
-  default     = 100
-}
-
-variable "db_instance_class" {
-  type        = string
-  description = "Instance class for the database"
-  default     = "db.t3.medium"
-}
-
 variable "create_lb" {
   type        = bool
   description = "Create load balancer for SkyWalking UI"