diff --git a/.tflint.hcl b/.tflint.hcl index 26c436d1..c6aee4ce 100644 --- a/.tflint.hcl +++ b/.tflint.hcl @@ -8,7 +8,7 @@ plugin "aws" { } config { - module = true + call_module_type = true force = false } diff --git a/docs/helpers/amazon-opensearch.md b/docs/helpers/amazon-opensearch.md new file mode 100644 index 00000000..9b56501a --- /dev/null +++ b/docs/helpers/amazon-opensearch.md @@ -0,0 +1,140 @@ +# Creating a new Amazon OpenSearch Domain + +This example creates an Amazon OpenSearch Domain within a VPC, +including an EC2 proxy instance to grant access to the domain Dashboards page +from outside of the VPC. It serves the purpose of demonstrating a minimal OpenSearch +domain that will receive observability signals using AWS Distro for FluentBit or +AWS Distro for OpenTelemetry. Mind that a production deployment of Amazon OpenSearch would +require elements that are not present in this example. + +## Prerequisites + +!!! note + Make sure to complete the [prerequisites section](https://aws-observability.github.io/terraform-aws-observability-accelerator/concepts/#prerequisites) before proceeding. + This example is designed to be deployed at the same VPC of the EKS cluster that will be observed. It expects the private and public subnets to have a `Name` tag, with any value that includes either `private` or `public`. + +## Setup + +### 1. Download sources and initialize Terraform + +``` +git clone https://github.com/aws-observability/terraform-aws-observability-accelerator.git +cd terraform-aws-observability-accelerator/examples/managed-grafana-workspace +terraform init +``` + +### 2. AWS Region + +Specify the AWS Region where the resources will be deployed: + +```bash +export TF_VAR_aws_region=xxx +``` + +### 3. VPC ID + +Specify the id of the VPC where the resources will be deployed: + +```bash +export TF_VAR_vpc_id=xxx +``` + +## Deploy + +Simply run this command to deploy the example + +```bash +terraform apply +``` + +## Accessing OpenSearch Dashboards + +Get reverse proxy instance public DNS name: + +```bash +aws ec2 describe-instances --filter Name=tag:"aws:autoscaling:groupName",Values="reverse_proxy" \ + --output json --query 'Reservations[0].Instances[0].PublicDnsName' --region --no-cli-pager +``` + +Retrieve OpenSearch Dashboards access credentials: + +```bash +# Master user name +aws ssm get-parameter --with-decryption --output json --no-cli-pager \ + --query "Parameter.Value" --name /terraform-accelerator/opensearch/master-user-name + +# Master user password +aws ssm get-parameter --with-decryption --output json --no-cli-pager \ + --query "Parameter.Value" --name /terraform-accelerator/opensearch/master-user-password +``` + +Access the URL from Public DNS name and open OpenSearch Dashboards using the retrieved credentials. + +## Granting access to FluentBit + +To allow FluentBit to ingest logs into the Amazon OpenSearch domain, follow the instructions bellow. + +Get FluentBit Role ARN: + +```bash +SA=$( + kubectl -n aws-for-fluent-bit get daemonset aws-for-fluent-bit -o json | + jq -r .spec.template.spec.serviceAccount) +kubectl -n aws-for-fluent-bit get sa $SA -o json | + jq -r .metadata.annotations.'"eks.amazonaws.com/role-arn"' +``` + +Add FluentBut Role ARN as a backend role in OpenSearch: + +1. Access OpenSearch Dashboards. In the left menu, select **Security**. +2. In Security, select **Roles**. +3. In Roles, select **all access**. +4. In All access, select the tab **Mapped Users**, and them **Manage mapping**. +5. In Backend roles, click in **Add another backend role**. In the empty field, enter the FluentBit Role ARN retrieved before. + +## Granting access to Amazon Managed Grafana + +To allow Amazon Managed Grafana to access Amazon OpenSearch domain datasource, follow the instructions bellow. + +1. Connect the workspace to the VPC following [these instructions](https://docs.aws.amazon.com/grafana/latest/userguide/AMG-configure-vpc.html). +2. Add access to OpenSearch datasources by following [these instructions](https://docs.aws.amazon.com/grafana/latest/userguide/ES-adding-AWS-config.html). +3. Include the policy for listing OpenSearch collections: + ```bash + GRAFANA_WORKSPACE_ID= + GRAFANA_ROLE_ARN=$( + aws grafana describe-workspace --workspace-id $GRAFANA_WORKSPACE_ID \ + --output json --no-cli-pager | jq -r .workspace.workspaceRoleArn) + GRAFANA_ROLE=$(echo $GRAFANA_ROLE_ARN | cut -d/ -f3) + cat < policy.json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "aoss:ListCollections" + ], + "Resource": "*" + } + ] + } + EOF + + aws iam put-role-policy --role-name $GRAFANA_ROLE \ + --policy-name OpenSearchCollections --policy-document file://policy.json + ``` + +4. Enable the OpenSearch plugin by following [these instructions](https://docs.aws.amazon.com/grafana/latest/userguide/aws-datasources-plugin.html). +5. Access OpenSearch Dashboards. In the left menu, select **Security**. +6. In Security, select **Roles**. +7. In Roles, select **all access**. +8. In All access, select the tab **Mapped Users**, and them **Manage mapping**. +9. In Backend roles, click in **Add another backend role**. In the empty field, enter the Grafana Role ARN retrieved before. + +## Cleanup + +To clean up your environment, destroy the Terraform example by running + +```sh +terraform destroy +``` diff --git a/examples/amazon-opensearch-domain/ec2.tf b/examples/amazon-opensearch-domain/ec2.tf new file mode 100644 index 00000000..582b371c --- /dev/null +++ b/examples/amazon-opensearch-domain/ec2.tf @@ -0,0 +1,81 @@ +data "aws_ami" "reverse_proxy" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["al2023-ami-2023.*-kernel-6.1-x86_64"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } +} + +resource "aws_security_group" "reverse_proxy" { + name = "reverse_proxy" + description = "Allow TLS inbound traffic and all outbound traffic" + vpc_id = var.vpc_id + + tags = { + Name = "reverse_proxy" + } +} + +resource "aws_vpc_security_group_ingress_rule" "reverse_proxy_ipv4" { + security_group_id = aws_security_group.reverse_proxy.id + cidr_ipv4 = local.reverse_proxy_client_ip + from_port = 443 + ip_protocol = "tcp" + to_port = 443 +} + +resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv4" { + security_group_id = aws_security_group.reverse_proxy.id + cidr_ipv4 = "0.0.0.0/0" + ip_protocol = "-1" # semantically equivalent to all ports +} + +resource "aws_launch_template" "reverse_proxy" { + name = "reverse_proxy" + image_id = data.aws_ami.reverse_proxy.id + instance_type = "t2.medium" + network_interfaces { + associate_public_ip_address = var.expose_proxy + security_groups = [aws_security_group.reverse_proxy.id] + } + user_data = base64encode(templatefile("${path.module}/user_data.sh", { os_domain = module.opensearch.domain_endpoint })) + metadata_options { + http_tokens = "required" + } + block_device_mappings { + device_name = "/dev/xvda" + ebs { + encrypted = true + } + } + lifecycle { + create_before_destroy = true + } +} + +resource "aws_autoscaling_group" "reverse_proxy" { + name = aws_launch_template.reverse_proxy.name + max_size = 1 + min_size = 0 + desired_capacity = 1 + launch_template { + id = aws_launch_template.reverse_proxy.id + version = "$Latest" + } + vpc_zone_identifier = [local.public_subnet_id] + lifecycle { + create_before_destroy = true + } +} diff --git a/examples/amazon-opensearch-domain/locals.tf b/examples/amazon-opensearch-domain/locals.tf new file mode 100644 index 00000000..bb1c1e5e --- /dev/null +++ b/examples/amazon-opensearch-domain/locals.tf @@ -0,0 +1,11 @@ +resource "random_password" "opensearch_master_password" { + length = 16 + special = true + override_special = "!#$%&*()-_=+[]{}<>:?" +} + +locals { + opensearch_master_user_name = var.master_user_name + opensearch_master_user_password = var.master_user_password == "" ? random_password.opensearch_master_password.result : var.master_user_password + availability_zone = var.availability_zone == "" ? "${var.aws_region}a" : var.availability_zone +} diff --git a/examples/amazon-opensearch-domain/main.tf b/examples/amazon-opensearch-domain/main.tf new file mode 100644 index 00000000..bc741021 --- /dev/null +++ b/examples/amazon-opensearch-domain/main.tf @@ -0,0 +1,166 @@ +provider "aws" { + region = var.aws_region +} + +data "aws_availability_zones" "available" {} + +data "aws_vpc" "main" { + id = var.vpc_id +} + +data "aws_subnet" "private_subnet" { + vpc_id = var.vpc_id + availability_zone = local.availability_zone + filter { + name = "tag:Name" + values = [ + "*private*", + "*Private*" + ] + } +} + +data "aws_subnet" "public_subnet" { + vpc_id = var.vpc_id + availability_zone = local.availability_zone + filter { + name = "tag:Name" + values = [ + "*public*", + "*Public*" + ] + } +} + +locals { + region = var.aws_region + name = "aws-o11y-accelerator" + + vpc_cidr = data.aws_vpc.main.cidr_block + public_subnet_id = data.aws_subnet.public_subnet.id + private_subnet_id = data.aws_subnet.private_subnet.id + azs = slice(data.aws_availability_zones.available.names, 0, 3) + reverse_proxy_client_ip = var.reverse_proxy_client_ip + + tags = { + GithubRepo = "terraform-aws-observability-accelerator" + GithubOrg = "aws-observability" + } +} + +resource "aws_ssm_parameter" "opensearch_master_user_name" { + name = "/terraform-accelerator/opensearch/master-user-name" + type = "SecureString" + value = local.opensearch_master_user_name + + tags = { + environment = "production" + } +} + +resource "aws_ssm_parameter" "opensearch_master_user_password" { + name = "/terraform-accelerator/opensearch/master-user-password" + type = "SecureString" + value = local.opensearch_master_user_password + + tags = { + environment = "production" + } +} + +module "opensearch" { + source = "terraform-aws-modules/opensearch/aws" + + # Domain + advanced_options = { + "rest.action.multi.allow_explicit_index" = "true" + } + + advanced_security_options = { + enabled = true + anonymous_auth_enabled = false + internal_user_database_enabled = true + + master_user_options = { + master_user_name = local.opensearch_master_user_name + master_user_password = local.opensearch_master_user_password + } + } + + cluster_config = { + instance_count = 1 + dedicated_master_enabled = false + instance_type = "r6g.large.search" + + zone_awareness_enabled = false + } + + domain_endpoint_options = { + enforce_https = true + tls_security_policy = "Policy-Min-TLS-1-2-2019-07" + } + + domain_name = local.name + + ebs_options = { + ebs_enabled = true + iops = 3000 + throughput = 125 + volume_type = "gp3" + volume_size = 20 + } + + encrypt_at_rest = { + enabled = true + } + + engine_version = "OpenSearch_2.11" + + node_to_node_encryption = { + enabled = true + } + + software_update_options = { + auto_software_update_enabled = false + } + + vpc_options = { + subnet_ids = [local.private_subnet_id] + } + + # VPC endpoint + vpc_endpoints = { + one = { + subnet_ids = [local.private_subnet_id] + } + } + + security_group_rules = { + ingress_443 = { + type = "ingress" + description = "HTTPS access from VPC" + from_port = 443 + to_port = 443 + ip_protocol = "tcp" + cidr_ipv4 = local.vpc_cidr + } + } + + # Access policy + access_policy_statements = [ + { + effect = "Allow" + + principals = [{ + type = "AWS" + identifiers = ["*"] + }] + + actions = ["es:*"] + } + ] + tags = { + Terraform = "true" + Environment = "dev" + } +} diff --git a/examples/amazon-opensearch-domain/outputs.tf b/examples/amazon-opensearch-domain/outputs.tf new file mode 100644 index 00000000..43b7c3fd --- /dev/null +++ b/examples/amazon-opensearch-domain/outputs.tf @@ -0,0 +1,55 @@ +################################################################################ +# Domain +################################################################################ + +output "domain_arn" { + description = "The Amazon Resource Name (ARN) of the domain" + value = module.opensearch.domain_arn +} + +output "domain_id" { + description = "The unique identifier for the domain" + value = module.opensearch.domain_id +} + +output "domain_endpoint" { + description = "Domain-specific endpoint used to submit index, search, and data upload requests" + value = module.opensearch.domain_endpoint +} + +output "domain_dashboard_endpoint" { + description = "Domain-specific endpoint for Dashboard without https scheme" + value = module.opensearch.domain_dashboard_endpoint +} + +################################################################################ +# VPC Endpoint(s) +################################################################################ + +output "vpc_endpoints" { + description = "Map of VPC endpoints created and their attributes" + value = module.opensearch.vpc_endpoints +} + +################################################################################ +# CloudWatch Log Groups +################################################################################ + +output "cloudwatch_logs" { + description = "Map of CloudWatch log groups created and their attributes" + value = module.opensearch.cloudwatch_logs +} + +################################################################################ +# Security Group +################################################################################ + +output "security_group_arn" { + description = "Amazon Resource Name (ARN) of the security group" + value = module.opensearch.security_group_arn +} + +output "security_group_id" { + description = "ID of the security group" + value = module.opensearch.security_group_id +} diff --git a/examples/amazon-opensearch-domain/readme.md b/examples/amazon-opensearch-domain/readme.md new file mode 100644 index 00000000..2ac4838f --- /dev/null +++ b/examples/amazon-opensearch-domain/readme.md @@ -0,0 +1,71 @@ +# Amazon OpenSearch Domain Setup + +This example creates an Amazon OpenSearch domain in the same VPC of the EKS cluster, +and a proxy instance to Amazon OpenSearch Dashboards, to allow access from outside of the VPC. + +Step-by-step instructions available on our [docs site](https://aws-observability.github.io/terraform-aws-observability-accelerator/) + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [aws](#requirement\_aws) | >= 5.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 5.0.0 | +| [random](#provider\_random) | n/a | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [opensearch](#module\_opensearch) | terraform-aws-modules/opensearch/aws | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_autoscaling_group.reverse_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group) | resource | +| [aws_launch_template.reverse_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template) | resource | +| [aws_security_group.reverse_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | +| [aws_ssm_parameter.opensearch_master_user_name](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_ssm_parameter.opensearch_master_user_password](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource | +| [aws_vpc_security_group_egress_rule.allow_all_traffic_ipv4](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_egress_rule) | resource | +| [aws_vpc_security_group_ingress_rule.reverse_proxy_ipv4](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_security_group_ingress_rule) | resource | +| [random_password.opensearch_master_password](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/password) | resource | +| [aws_ami.reverse_proxy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ami) | data source | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_subnet.private_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | +| [aws_subnet.public_subnet](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet) | data source | +| [aws_vpc.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [availability\_zone](#input\_availability\_zone) | AZ where the example domain and its proxy instance will be created | `string` | `""` | no | +| [aws\_region](#input\_aws\_region) | AWS Region | `string` | n/a | yes | +| [expose\_proxy](#input\_expose\_proxy) | Whether or not to expose EC2 proxy instance for Amazon Opensearch dashboards to the Internet | `bool` | `false` | no | +| [master\_user\_name](#input\_master\_user\_name) | OpenSearch domain user name | `string` | `"observability-accelerator"` | no | +| [master\_user\_password](#input\_master\_user\_password) | OpenSearch domain password | `string` | `""` | no | +| [reverse\_proxy\_client\_ip](#input\_reverse\_proxy\_client\_ip) | CIDR block to grant access for OpenSearch reverse proxy | `string` | `"0.0.0.0/0"` | no | +| [vpc\_id](#input\_vpc\_id) | EKS cluster VPC Id | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [cloudwatch\_logs](#output\_cloudwatch\_logs) | Map of CloudWatch log groups created and their attributes | +| [domain\_arn](#output\_domain\_arn) | The Amazon Resource Name (ARN) of the domain | +| [domain\_dashboard\_endpoint](#output\_domain\_dashboard\_endpoint) | Domain-specific endpoint for Dashboard without https scheme | +| [domain\_endpoint](#output\_domain\_endpoint) | Domain-specific endpoint used to submit index, search, and data upload requests | +| [domain\_id](#output\_domain\_id) | The unique identifier for the domain | +| [security\_group\_arn](#output\_security\_group\_arn) | Amazon Resource Name (ARN) of the security group | +| [security\_group\_id](#output\_security\_group\_id) | ID of the security group | +| [vpc\_endpoints](#output\_vpc\_endpoints) | Map of VPC endpoints created and their attributes | + diff --git a/examples/amazon-opensearch-domain/user_data.sh b/examples/amazon-opensearch-domain/user_data.sh new file mode 100644 index 00000000..3a98f1a9 --- /dev/null +++ b/examples/amazon-opensearch-domain/user_data.sh @@ -0,0 +1,44 @@ +#!/bin/bash +yum update -y +yum install jq -y +yum install nginx.x86_64 -y +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/cert.key -out /etc/nginx/cert.crt -subj /C=US/ST=./L=./O=./CN=.\n + +cat << EOF > /etc/nginx/conf.d/nginx_opensearch.conf +server { + listen 443 ssl; + server_name \$host; + rewrite ^/$ https://\$host/_dashboards redirect; + + # openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/nginx/cert.key -out /etc/nginx/cert.crt -subj /C=US/ST=./L=./O=./CN=.\n + ssl_certificate /etc/nginx/cert.crt; + ssl_certificate_key /etc/nginx/cert.key; + + ssl_session_cache builtin:1000 shared:SSL:10m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4; + ssl_prefer_server_ciphers on; + + + location ^~ /_dashboards { + # Forward requests to OpenSearch Dashboards + proxy_pass https://DOMAIN_ENDPOINT/_dashboards; + + # Update cookie domain and path + proxy_cookie_domain DOMAIN_ENDPOINT \$host; + + proxy_set_header Accept-Encoding ""; + sub_filter_types *; + sub_filter DOMAIN_ENDPOINT \$host; + sub_filter_once off; + + # Response buffer settings + proxy_buffer_size 128k; + proxy_buffers 4 256k; + proxy_busy_buffers_size 256k; + } +} +EOF +sed -i -e "s/DOMAIN_ENDPOINT/${os_domain}/g" /etc/nginx/conf.d/nginx_opensearch.conf +systemctl restart nginx.service +systemctl enable nginx.service diff --git a/examples/amazon-opensearch-domain/variables.tf b/examples/amazon-opensearch-domain/variables.tf new file mode 100644 index 00000000..ca4f135c --- /dev/null +++ b/examples/amazon-opensearch-domain/variables.tf @@ -0,0 +1,39 @@ +variable "aws_region" { + description = "AWS Region" + type = string +} + +variable "vpc_id" { + description = "EKS cluster VPC Id" + type = string +} + +variable "master_user_name" { + description = "OpenSearch domain user name" + type = string + default = "observability-accelerator" +} +variable "master_user_password" { + description = "OpenSearch domain password" + type = string + sensitive = true + default = "" +} + +variable "reverse_proxy_client_ip" { + description = "CIDR block to grant access for OpenSearch reverse proxy" + type = string + default = "0.0.0.0/0" +} + +variable "availability_zone" { + description = "AZ where the example domain and its proxy instance will be created" + type = string + default = "" +} + +variable "expose_proxy" { + description = "Whether or not to expose EC2 proxy instance for Amazon Opensearch dashboards to the Internet" + type = bool + default = false +} diff --git a/examples/amazon-opensearch-domain/versions.tf b/examples/amazon-opensearch-domain/versions.tf new file mode 100644 index 00000000..f09cdf67 --- /dev/null +++ b/examples/amazon-opensearch-domain/versions.tf @@ -0,0 +1,11 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.0.0" + } + } + +} diff --git a/examples/eks-managed-open-source-observability/README.md b/examples/eks-managed-open-source-observability/README.md new file mode 100644 index 00000000..f5acb9fa --- /dev/null +++ b/examples/eks-managed-open-source-observability/README.md @@ -0,0 +1,73 @@ +# Existing Cluster with the AWS Observability accelerator EKS Infrastructure monitoring and OpenSearch logs + +This example demonstrates how to use the AWS Observability Accelerator Terraform +modules with Infrastructure monitoring enabled. +The current example deploys the [AWS Distro for OpenTelemetry Operator](https://docs.aws.amazon.com/eks/latest/userguide/opentelemetry.html) +for Amazon EKS with its requirements and make use of an existing Amazon Managed Grafana workspace. +It creates a new Amazon Managed Service for Prometheus workspace unless provided with an existing one to reuse. + +It uses the `EKS monitoring` [module](../../modules/eks-monitoring/) +to provide an existing EKS cluster with an OpenTelemetry collector, +curated Grafana dashboards, Prometheus alerting and recording rules with multiple +configuration options on the cluster infrastructure. + +In addition, logs are shipped to an OpenSearch domain. + +View the full documentation for this example [here](https://aws-observability.github.io/terraform-aws-observability-accelerator/eks/) + +For an implemantion of Amazon OpenSearch that will work with this example, follow [these instructions](https://aws-observability.github.io/terraform-aws-observability-accelerator/helpers/amazon-opensearch/). + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.1.0 | +| [aws](#requirement\_aws) | >= 4.0.0 | +| [helm](#requirement\_helm) | >= 2.4.1 | +| [kubectl](#requirement\_kubectl) | >= 2.0.3 | +| [kubernetes](#requirement\_kubernetes) | >= 2.10 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 4.0.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [eks\_monitoring](#module\_eks\_monitoring) | ../../modules/eks-monitoring | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_eks_cluster.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster) | data source | +| [aws_eks_cluster_auth.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/eks_cluster_auth) | data source | +| [aws_grafana_workspace.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/grafana_workspace) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_region](#input\_aws\_region) | AWS Region | `string` | n/a | yes | +| [eks\_cluster\_id](#input\_eks\_cluster\_id) | Name of the EKS cluster | `string` | `"eks-cluster-with-vpc"` | no | +| [enable\_dashboards](#input\_enable\_dashboards) | Enables or disables curated dashboards. Dashboards are managed by the Grafana Operator | `bool` | `true` | no | +| [grafana\_api\_key](#input\_grafana\_api\_key) | API key for authorizing the Grafana provider to make changes to Amazon Managed Grafana | `string` | n/a | yes | +| [managed\_grafana\_workspace\_id](#input\_managed\_grafana\_workspace\_id) | Amazon Managed Grafana Workspace ID | `string` | n/a | yes | +| [managed\_prometheus\_workspace\_id](#input\_managed\_prometheus\_workspace\_id) | Amazon Managed Service for Prometheus Workspace ID | `string` | `""` | no | +| [os\_logs\_host](#input\_os\_logs\_host) | OpenSearch domain URL for logs | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [eks\_cluster\_id](#output\_eks\_cluster\_id) | EKS Cluster Id | +| [eks\_cluster\_version](#output\_eks\_cluster\_version) | EKS Cluster version | +| [fluentbit\_irsa\_arn](#output\_fluentbit\_irsa\_arn) | IRSA Arn for FluentBit | +| [managed\_prometheus\_workspace\_endpoint](#output\_managed\_prometheus\_workspace\_endpoint) | Amazon Managed Prometheus workspace endpoint | +| [managed\_prometheus\_workspace\_id](#output\_managed\_prometheus\_workspace\_id) | Amazon Managed Prometheus workspace ID | +| [managed\_prometheus\_workspace\_region](#output\_managed\_prometheus\_workspace\_region) | AWS Region | + diff --git a/examples/eks-managed-open-source-observability/cleanup.sh b/examples/eks-managed-open-source-observability/cleanup.sh new file mode 100755 index 00000000..1c9402ae --- /dev/null +++ b/examples/eks-managed-open-source-observability/cleanup.sh @@ -0,0 +1,31 @@ +#!/bin/bash +set -o errexit +set -o pipefail + +read -p "Enter the region: " region +export AWS_DEFAULT_REGION=$region + +targets=( +"module.eks_monitoring" +) + +for target in "${targets[@]}" +do + terraform destroy -target="$target" -auto-approve + destroy_output=$(terraform destroy -target="$target" -auto-approve 2>&1) + if [[ $? -eq 0 && $destroy_output == *"Destroy complete!"* ]]; then + echo "SUCCESS: Terraform destroy of $target completed successfully" + else + echo "FAILED: Terraform destroy of $target failed" + exit 1 + fi +done + +terraform destroy -auto-approve +destroy_output=$(terraform destroy -auto-approve 2>&1) +if [[ $? -eq 0 && $destroy_output == *"Destroy complete!"* ]]; then + echo "SUCCESS: Terraform destroy of all targets completed successfully" +else + echo "FAILED: Terraform destroy of all targets failed" + exit 1 +fi diff --git a/examples/eks-managed-open-source-observability/install.sh b/examples/eks-managed-open-source-observability/install.sh new file mode 100755 index 00000000..a94380b6 --- /dev/null +++ b/examples/eks-managed-open-source-observability/install.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +echo "Initializing ..." +terraform init || echo "\"terraform init\" failed" + +# List of Terraform modules to apply in sequence +targets=( + "module.eks_monitoring" +) + +# Apply modules in sequence +for target in "${targets[@]}" +do + echo "Applying module $target..." + apply_output=$(terraform apply -target="$target" -auto-approve 2>&1 | tee /dev/tty) + if [[ ${PIPESTATUS[0]} -eq 0 && $apply_output == *"Apply complete"* ]]; then + echo "SUCCESS: Terraform apply of $target completed successfully" + else + echo "FAILED: Terraform apply of $target failed" + exit 1 + fi +done + +# Final apply to catch any remaining resources +echo "Applying remaining resources..." +apply_output=$(terraform apply -auto-approve 2>&1 | tee /dev/tty) +if [[ ${PIPESTATUS[0]} -eq 0 && $apply_output == *"Apply complete"* ]]; then + echo "SUCCESS: Terraform apply of all modules completed successfully" +else + echo "FAILED: Terraform apply of all modules failed" + exit 1 +fi diff --git a/examples/eks-managed-open-source-observability/main.tf b/examples/eks-managed-open-source-observability/main.tf new file mode 100644 index 00000000..3e7810ee --- /dev/null +++ b/examples/eks-managed-open-source-observability/main.tf @@ -0,0 +1,83 @@ +provider "aws" { + region = local.region +} + +data "aws_eks_cluster_auth" "this" { + name = var.eks_cluster_id +} + +data "aws_eks_cluster" "this" { + name = var.eks_cluster_id +} + +data "aws_grafana_workspace" "this" { + workspace_id = var.managed_grafana_workspace_id +} + +provider "kubernetes" { + host = local.eks_cluster_endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data) + token = data.aws_eks_cluster_auth.this.token +} + +provider "helm" { + kubernetes { + host = local.eks_cluster_endpoint + cluster_ca_certificate = base64decode(data.aws_eks_cluster.this.certificate_authority[0].data) + token = data.aws_eks_cluster_auth.this.token + } +} + +locals { + region = var.aws_region + eks_cluster_endpoint = data.aws_eks_cluster.this.endpoint + create_new_workspace = var.managed_prometheus_workspace_id == "" ? true : false + tags = { + Source = "github.com/aws-observability/terraform-aws-observability-accelerator" + } +} + +module "eks_monitoring" { + source = "../../modules/eks-monitoring" + # source = "github.com/aws-observability/terraform-aws-observability-accelerator//modules/eks-monitoring?ref=v2.0.0" + + eks_cluster_id = var.eks_cluster_id + + # deploys AWS Distro for OpenTelemetry operator into the cluster + enable_amazon_eks_adot = true + + # reusing existing certificate manager? defaults to true + enable_cert_manager = true + + # enable EKS API server monitoring + enable_apiserver_monitoring = true + + # deploys external-secrets in to the cluster + enable_external_secrets = true + grafana_api_key = var.grafana_api_key + target_secret_name = "grafana-admin-credentials" + target_secret_namespace = "grafana-operator" + grafana_url = "https://${data.aws_grafana_workspace.this.endpoint}" + + # control the publishing of dashboards by specifying the boolean value for the variable 'enable_dashboards', default is 'true' + enable_dashboards = var.enable_dashboards + + # creates a new Amazon Managed Prometheus workspace, defaults to true + enable_managed_prometheus = local.create_new_workspace + managed_prometheus_workspace_id = var.managed_prometheus_workspace_id + + # sets up the Amazon Managed Prometheus alert manager at the workspace level + enable_alertmanager = true + + # optional, defaults to 60s interval and 15s timeout + prometheus_config = { + global_scrape_interval = "60s" + global_scrape_timeout = "15s" + } + + enable_logs = true + os_logs_enabled = true + os_logs_host = var.os_logs_host + + tags = local.tags +} diff --git a/examples/eks-managed-open-source-observability/outputs.tf b/examples/eks-managed-open-source-observability/outputs.tf new file mode 100644 index 00000000..6712e58b --- /dev/null +++ b/examples/eks-managed-open-source-observability/outputs.tf @@ -0,0 +1,29 @@ +output "managed_prometheus_workspace_region" { + description = "AWS Region" + value = module.eks_monitoring.managed_prometheus_workspace_region +} + +output "managed_prometheus_workspace_endpoint" { + description = "Amazon Managed Prometheus workspace endpoint" + value = module.eks_monitoring.managed_prometheus_workspace_endpoint +} + +output "managed_prometheus_workspace_id" { + description = "Amazon Managed Prometheus workspace ID" + value = module.eks_monitoring.managed_prometheus_workspace_id +} + +output "eks_cluster_version" { + description = "EKS Cluster version" + value = module.eks_monitoring.eks_cluster_version +} + +output "eks_cluster_id" { + description = "EKS Cluster Id" + value = module.eks_monitoring.eks_cluster_id +} + +output "fluentbit_irsa_arn" { + description = "IRSA Arn for FluentBit" + value = module.eks_monitoring.fluentbit_irsa_arn +} diff --git a/examples/eks-managed-open-source-observability/variables.tf b/examples/eks-managed-open-source-observability/variables.tf new file mode 100644 index 00000000..e14404b8 --- /dev/null +++ b/examples/eks-managed-open-source-observability/variables.tf @@ -0,0 +1,39 @@ +variable "eks_cluster_id" { + description = "Name of the EKS cluster" + type = string + default = "eks-cluster-with-vpc" +} + +variable "aws_region" { + description = "AWS Region" + type = string +} + +variable "managed_prometheus_workspace_id" { + description = "Amazon Managed Service for Prometheus Workspace ID" + type = string + default = "" +} + +variable "managed_grafana_workspace_id" { + description = "Amazon Managed Grafana Workspace ID" + type = string +} + +variable "grafana_api_key" { + description = "API key for authorizing the Grafana provider to make changes to Amazon Managed Grafana" + type = string + sensitive = true +} + +variable "enable_dashboards" { + description = "Enables or disables curated dashboards. Dashboards are managed by the Grafana Operator" + type = bool + default = true +} + +variable "os_logs_host" { + description = "OpenSearch domain URL for logs" + type = string + default = "" +} diff --git a/examples/eks-managed-open-source-observability/versions.tf b/examples/eks-managed-open-source-observability/versions.tf new file mode 100644 index 00000000..55281dba --- /dev/null +++ b/examples/eks-managed-open-source-observability/versions.tf @@ -0,0 +1,30 @@ +terraform { + required_version = ">= 1.1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.10" + } + kubectl = { + source = "alekc/kubectl" + version = ">= 2.0.3" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.4.1" + } + } + + # ## Used for end-to-end testing on project; update to suit your needs + # backend "s3" { + # bucket = "aws-observability-accelerator-terraform-states" + # region = "us-west-2" + # key = "e2e/existing-cluster-with-base-and-infra/terraform.tfstate" + # } + +} diff --git a/mkdocs.yml b/mkdocs.yml index 359fc6e7..c50bc1a9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -51,6 +51,7 @@ nav: - EKS Cluster with VPC: helpers/new-eks-cluster.md - Amazon Managed Grafana setup: helpers/managed-grafana.md - ECS Cluster with VPC: helpers/ecs-cluster-with-vpc.md + - Amazon OpenSearch setup: helpers/amazon-opensearch.md - Support & Feedback: support.md - Contributors: contributors.md diff --git a/modules/eks-monitoring/README.md b/modules/eks-monitoring/README.md index 1c984667..7bd6eb0c 100644 --- a/modules/eks-monitoring/README.md +++ b/modules/eks-monitoring/README.md @@ -131,6 +131,11 @@ See examples using this Terraform modules in the **Amazon EKS** section of [this | [ne\_config](#input\_ne\_config) | Node exporter configuration |
object({
create_namespace = optional(bool, true)
k8s_namespace = optional(string, "prometheus-node-exporter")
helm_chart_name = optional(string, "prometheus-node-exporter")
helm_chart_version = optional(string, "4.24.0")
helm_release_name = optional(string, "prometheus-node-exporter")
helm_repo_url = optional(string, "https://prometheus-community.github.io/helm-charts")
helm_settings = optional(map(string), {})
helm_values = optional(map(any), {})

scrape_interval = optional(string, "60s")
scrape_timeout = optional(string, "60s")
})
| `{}` | no | | [nginx\_config](#input\_nginx\_config) | Configuration object for NGINX monitoring |
object({
enable_alerting_rules = optional(bool)
enable_recording_rules = optional(bool)
enable_dashboards = optional(bool)
scrape_sample_limit = optional(number)

flux_gitrepository_name = optional(string)
flux_gitrepository_url = optional(string)
flux_gitrepository_branch = optional(string)
flux_kustomization_name = optional(string)
flux_kustomization_path = optional(string)

grafana_dashboard_url = optional(string)

prometheus_metrics_endpoint = optional(string)
})
| `{}` | no | | [nvidia\_monitoring\_config](#input\_nvidia\_monitoring\_config) | Config object for nvidia monitoring |
object({
flux_gitrepository_name = string
flux_gitrepository_url = string
flux_gitrepository_branch = string
flux_kustomization_name = string
flux_kustomization_path = string
})
| `null` | no | +| [opensearch\_config](#input\_opensearch\_config) | Config object for API server monitoring |
object({
flux_gitrepository_name = string
flux_gitrepository_url = string
flux_gitrepository_branch = string
flux_kustomization_name = string
flux_kustomization_path = string

dashboards = object({
logs = string
})
})
| `null` | no | +| [os\_logs\_enabled](#input\_os\_logs\_enabled) | FluentBit OpenSearch enable | `bool` | `false` | no | +| [os\_logs\_host](#input\_os\_logs\_host) | FluentBit OpenSearch | `string` | `""` | no | +| [os\_logs\_index](#input\_os\_logs\_index) | FluentBit OpenSearch | `string` | `"observability-accelerator"` | no | +| [os\_logs\_region](#input\_os\_logs\_region) | OpenSearch Domain Region | `string` | `null` | no | | [prometheus\_config](#input\_prometheus\_config) | Controls default values such as scrape interval, timeouts and ports globally |
object({
global_scrape_interval = optional(string, "120s")
global_scrape_timeout = optional(string, "15s")
})
| `{}` | no | | [tags](#input\_tags) | Additional tags (e.g. `map('BusinessUnit`,`XYZ`) | `map(string)` | `{}` | no | | [target\_secret\_name](#input\_target\_secret\_name) | Target secret in Kubernetes to store the Grafana API Key Secret | `string` | `"grafana-admin-credentials"` | no | @@ -144,6 +149,7 @@ See examples using this Terraform modules in the **Amazon EKS** section of [this | [adot\_irsa\_arn](#output\_adot\_irsa\_arn) | IRSA Arn for ADOT | | [eks\_cluster\_id](#output\_eks\_cluster\_id) | EKS Cluster Id | | [eks\_cluster\_version](#output\_eks\_cluster\_version) | EKS Cluster version | +| [fluentbit\_irsa\_arn](#output\_fluentbit\_irsa\_arn) | IRSA Arn for FluentBit | | [managed\_prometheus\_workspace\_endpoint](#output\_managed\_prometheus\_workspace\_endpoint) | Amazon Managed Prometheus workspace endpoint | | [managed\_prometheus\_workspace\_id](#output\_managed\_prometheus\_workspace\_id) | Amazon Managed Prometheus workspace ID | | [managed\_prometheus\_workspace\_region](#output\_managed\_prometheus\_workspace\_region) | Amazon Managed Prometheus workspace region | diff --git a/modules/eks-monitoring/add-ons/aws-for-fluentbit/README.md b/modules/eks-monitoring/add-ons/aws-for-fluentbit/README.md index 8b37ec6a..ae067221 100644 --- a/modules/eks-monitoring/add-ons/aws-for-fluentbit/README.md +++ b/modules/eks-monitoring/add-ons/aws-for-fluentbit/README.md @@ -39,9 +39,13 @@ See this [Helm Chart](https://github.com/aws/eks-charts/tree/master/stable/aws-f |------|-------------|------|---------|:--------:| | [addon\_context](#input\_addon\_context) | Input configuration for the addon |
object({
aws_caller_identity_account_id = string
aws_caller_identity_arn = string
aws_eks_cluster_endpoint = string
aws_partition_id = string
aws_region_name = string
eks_cluster_id = string
eks_oidc_issuer_url = string
eks_oidc_provider_arn = string
tags = map(string)
irsa_iam_role_path = string
irsa_iam_permissions_boundary = string
})
| n/a | yes | | [cw\_log\_retention\_days](#input\_cw\_log\_retention\_days) | FluentBit CloudWatch Log group retention period | `number` | `90` | no | +| [cw\_logs\_enabled](#input\_cw\_logs\_enabled) | FluentBit CloudWatch Log enable | `bool` | `true` | no | | [helm\_config](#input\_helm\_config) | Helm provider config aws\_for\_fluent\_bit. | `any` | `{}` | no | | [irsa\_policies](#input\_irsa\_policies) | Additional IAM policies for a IAM role for service accounts | `list(string)` | `[]` | no | | [manage\_via\_gitops](#input\_manage\_via\_gitops) | Determines if the add-on should be managed via GitOps. | `bool` | `false` | no | +| [os\_logs\_enabled](#input\_os\_logs\_enabled) | FluentBit OpenSearch enable | `bool` | `false` | no | +| [os\_logs\_host](#input\_os\_logs\_host) | FluentBit OpenSearch | `string` | `""` | no | +| [os\_logs\_index](#input\_os\_logs\_index) | FluentBit OpenSearch | `string` | `"observability-accelerator"` | no | | [refresh\_interval](#input\_refresh\_interval) | FluentBit input refresh interval | `number` | `60` | no | ## Outputs diff --git a/modules/eks-monitoring/add-ons/aws-for-fluentbit/locals.tf b/modules/eks-monitoring/add-ons/aws-for-fluentbit/locals.tf index e77d609e..31d79960 100644 --- a/modules/eks-monitoring/add-ons/aws-for-fluentbit/locals.tf +++ b/modules/eks-monitoring/add-ons/aws-for-fluentbit/locals.tf @@ -35,6 +35,10 @@ locals { log_retention_days = var.cw_log_retention_days refresh_interval = var.refresh_interval service_account = local.service_account + cw_logs_enabled = var.cw_logs_enabled + os_logs_enabled = var.os_logs_enabled + os_logs_host = var.os_logs_host + os_logs_index = var.os_logs_index })] irsa_config = { diff --git a/modules/eks-monitoring/add-ons/aws-for-fluentbit/values.yaml b/modules/eks-monitoring/add-ons/aws-for-fluentbit/values.yaml index be4afb9f..b5ea504f 100644 --- a/modules/eks-monitoring/add-ons/aws-for-fluentbit/values.yaml +++ b/modules/eks-monitoring/add-ons/aws-for-fluentbit/values.yaml @@ -6,7 +6,7 @@ cloudWatch: enabled: false cloudWatchLogs: - enabled: true + enabled: ${cw_logs_enabled} region: ${aws_region} # logGroupName is a fallback to failed parsing logGroupName: /aws/eks/observability-accelerator/workloads @@ -15,6 +15,13 @@ cloudWatchLogs: logKey: log logRetentionDays: ${log_retention_days} +opensearch: + enabled: ${os_logs_enabled} + match: "*" + awsRegion: ${aws_region} + host: ${os_logs_host} + index: ${os_logs_index} + input: enabled: false diff --git a/modules/eks-monitoring/add-ons/aws-for-fluentbit/variables.tf b/modules/eks-monitoring/add-ons/aws-for-fluentbit/variables.tf index cbab80a1..28f327cd 100644 --- a/modules/eks-monitoring/add-ons/aws-for-fluentbit/variables.tf +++ b/modules/eks-monitoring/add-ons/aws-for-fluentbit/variables.tf @@ -10,6 +10,30 @@ variable "cw_log_retention_days" { default = 90 } +variable "cw_logs_enabled" { + description = "FluentBit CloudWatch Log enable" + type = bool + default = true +} + +variable "os_logs_enabled" { + description = "FluentBit OpenSearch enable" + type = bool + default = false +} + +variable "os_logs_host" { + description = "FluentBit OpenSearch" + type = string + default = "" +} + +variable "os_logs_index" { + description = "FluentBit OpenSearch" + type = string + default = "observability-accelerator" +} + variable "refresh_interval" { description = "FluentBit input refresh interval" type = number diff --git a/modules/eks-monitoring/dashboards.tf b/modules/eks-monitoring/dashboards.tf index 80b98b5c..5aa77ab2 100644 --- a/modules/eks-monitoring/dashboards.tf +++ b/modules/eks-monitoring/dashboards.tf @@ -72,6 +72,31 @@ YAML depends_on = [module.external_secrets] } +# opensearch dashboards +# resource "kubectl_manifest" "opensearch_dashboards" { +# yaml_body = <