diff --git a/.gitignore b/.gitignore index 6304eb3..2b78eff 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ override.tf.json # Ignore CLI configuration files .terraformrc terraform.rc + +.terraform.lock.hcl +/node_modules diff --git a/README.md b/README.md index 3925921..f6d6a9a 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,55 @@ # terraform-onpremise-grafana +https://registry.terraform.io/modules/dasmeta/grafana/onpremise/latest + This module is created to manage OnPremise Grafana stack with Terraform. At this moment we support managing +- Grafana Dashboard with `dashboard` submodule - Grafana Alerts with `alerts` submodule - Grafana Contact Points with `contact-points` submodule - Grafana Notification Policies with `notifications` submodule More parts are coming soon. +## example for dashboard +```hcl +module "grafana_monitoring" { + source = "dasmeta/grafana/onpremise" + version = "1.2.0" + + name = "Test-dashboard" + + application_dashboard = { + rows : [ + { type : "block/sla" }, + { type : "block/ingress" }, + { type : "block/service", name : "service-name-1", host : "example.com" }, + { type : "block/service", name : "service-name-2" }, + { type : "block/service", name : "service-name-3" } + ] + data_source = { + uid : "00000" + } + variables = [ + { + "name" : "namespace", + "options" : [ + { + "selected" : true, + "value" : "prod" + }, + { + "value" : "stage" + }, + { + "value" : "dev" + } + ], + } + ] + } +} +``` + ## Example for Alert Rules ``` module "grafana_alerts" { @@ -161,11 +204,14 @@ module "grafana_alerts" { ``` ## Usage -Check `modules/alerts/tests`, `modules/contact-points/tests` and `modules/notifications/tests` folders to see more examples. +Check `./tests`, `modules/alerts/tests`, `modules/contact-points/tests` and `modules/notifications/tests` folders to see more examples. ## Requirements -No requirements. +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [grafana](#requirement\_grafana) | >= 3.7.0 | ## Providers @@ -176,6 +222,7 @@ No providers. | Name | Source | Version | |------|--------|---------| | [alerts](#module\_alerts) | ./modules/alerts | n/a | +| [application\_dashboard](#module\_application\_dashboard) | ./modules/dashboard/ | n/a | | [contact\_points](#module\_contact\_points) | ./modules/contact-points | n/a | | [notifications](#module\_notifications) | ./modules/notifications | n/a | @@ -188,12 +235,16 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [alert\_interval\_seconds](#input\_alert\_interval\_seconds) | The interval, in seconds, at which all rules in the group are evaluated. If a group contains many rules, the rules are evaluated sequentially. | `number` | `10` | no | -| [alert\_rules](#input\_alert\_rules) | This varibale describes alert folders, groups and rules. |
list(object({
name = string # The name of the alert rule
no_data_state = optional(string, "NoData") # Describes what state to enter when the rule's query returns No Data
exec_err_state = optional(string, "Error") # Describes what state to enter when the rule's query is invalid and the rule cannot be executed
summary = optional(string, "") # Rule annotation as a summary
priority = optional(string, "P2") # Rule priority level: P2 is for non-critical alerts, P1 will be set for critical alerts
folder_name = optional(string, "Main Alerts") # Grafana folder name in which the rule will be created
datasource = string # Name of the datasource used for the alert
expr = optional(string, null) # Full expression for the alert
metric_name = optional(string, "") # Prometheus metric name which queries the data for the alert
metric_function = optional(string, "") # Prometheus function used with metric for queries, like rate, sum etc.
metric_interval = optional(string, "") # The time interval with using functions like rate
settings_mode = optional(string, "replaceNN") # The mode used in B block, possible values are Strict, replaceNN, dropNN
settings_replaceWith = optional(number, 0) # The value by which NaN results of the query will be replaced
filters = optional(any, {}) # Filters object to identify each service for alerting
function = optional(string, "mean") # One of Reduce functions which will be used in B block for alerting
equation = string # The equation in the math expression which compares B blocks value with a number and generates an alert if needed. Possible values: gt, lt, gte, lte, e
threshold = number # The value against which B blocks are compared in the math expression
}))
| `[]` | no | +| [alert\_rules](#input\_alert\_rules) | This variable describes alert folders, groups and rules. |
list(object({
name = string # The name of the alert rule
no_data_state = optional(string, "NoData") # Describes what state to enter when the rule's query returns No Data
exec_err_state = optional(string, "Error") # Describes what state to enter when the rule's query is invalid and the rule cannot be executed
summary = optional(string, "") # Rule annotation as a summary
priority = optional(string, "P2") # Rule priority level: P2 is for non-critical alerts, P1 will be set for critical alerts
folder_name = optional(string, "Main Alerts") # Grafana folder name in which the rule will be created
datasource = string # Name of the datasource used for the alert
expr = optional(string, null) # Full expression for the alert
metric_name = optional(string, "") # Prometheus metric name which queries the data for the alert
metric_function = optional(string, "") # Prometheus function used with metric for queries, like rate, sum etc.
metric_interval = optional(string, "") # The time interval with using functions like rate
settings_mode = optional(string, "replaceNN") # The mode used in B block, possible values are Strict, replaceNN, dropNN
settings_replaceWith = optional(number, 0) # The value by which NaN results of the query will be replaced
filters = optional(any, {}) # Filters object to identify each service for alerting
function = optional(string, "mean") # One of Reduce functions which will be used in B block for alerting
equation = string # The equation in the math expression which compares B blocks value with a number and generates an alert if needed. Possible values: gt, lt, gte, lte, e
threshold = number # The value against which B blocks are compared in the math expression
}))
| `[]` | no | +| [application\_dashboard](#input\_application\_dashboard) | Dashboard for monitoring applications |
object({
rows = optional(any, [])
data_source = object({ # global/default datasource, TODO: create datasource inside the module
uid = string
type = optional(string, "prometheus")
})
variables = optional(list(object({ # Allows to define variables to be used in dashboard
name = string
type = optional(string, "custom")
hide = optional(number, 0)
includeAll = optional(bool, false)
multi = optional(bool, false)
query = optional(string, "")
queryValue = optional(string, "")
skipUrlSync = optional(bool, false)
options = optional(list(object({
selected = optional(bool, false)
value = string
text = optional(string, null)
})), [])
})), [])
})
|
{
"data_source": null,
"rows": [],
"variables": []
}
| no | +| [name](#input\_name) | Dashboard name | `string` | n/a | yes | | [notifications](#input\_notifications) | Represents the configuration options for Grafana notification policies. |
object({
contact_point = optional(string, "Slack") # The default contact point to route all unmatched notifications to.
group_by = optional(list(string), ["grafana_folder", "alertname"]) # A list of alert labels to group alerts into notifications by.
group_interval = optional(string, "5m") # Minimum time interval between two notifications for the same group.
repeat_interval = optional(string, "4h") # Minimum time interval for re-sending a notification if an alert is still firing.

policy = optional(object({
contact_point = optional(string, null) # The contact point to route notifications that match this rule to.
continue = optional(bool, false) # Whether to continue matching subsequent rules if an alert matches the current rule. Otherwise, the rule will be 'consumed' by the first policy to match it.
group_by = optional(list(string), [])
mute_timings = optional(list(string), []) # A list of mute timing names to apply to alerts that match this policy.

matcher = optional(object({
label = optional(string, "priority") # The name of the label to match against.
match = optional(string, "=") # The operator to apply when matching values of the given label. Allowed operators are = for equality, != for negated equality, =~ for regex equality, and !~ for negated regex equality.
value = optional(string, "P1") # The label value to match against.
}))
}))
})
| `{}` | no | | [opsgenie\_endpoints](#input\_opsgenie\_endpoints) | OpsGenie contact points list. |
list(object({
name = string # The name of the contact point.
api_key = string # The OpsGenie API key to use.
auto_close = optional(bool, false) # Whether to auto-close alerts in OpsGenie when they resolve in the Alertmanager.
message = optional(string, "") # The templated content of the message.
api_url = optional(string, "https://api.opsgenie.com/v2/alerts") # Allows customization of the OpsGenie API URL.
disable_resolve_message = optional(bool, false) # Whether to disable sending resolve messages.
}))
| `[]` | no | | [slack\_endpoints](#input\_slack\_endpoints) | Slack contact points list. |
list(object({
name = string # The name of the contact point.
endpoint_url = optional(string, "https://slack.com/api/chat.postMessage") # Use this to override the Slack API endpoint URL to send requests to.
icon_emoji = optional(string, "") # The name of a Slack workspace emoji to use as the bot icon.
icon_url = optional(string, "") # A URL of an image to use as the bot icon.
recipient = optional(string, null) # Channel, private group, or IM channel (can be an encoded ID or a name) to send messages to.
text = optional(string, "") # Templated content of the message.
title = optional(string, "") # Templated title of the message.
token = optional(string, "") # A Slack API token,for sending messages directly without the webhook method.
webhook_url = optional(string, "") # A Slack webhook URL,for sending messages via the webhook method.
username = optional(string, "") # Username for the bot to use.
disable_resolve_message = optional(bool, false) # Whether to disable sending resolve messages.
}))
| `[]` | no | ## Outputs -No outputs. +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | diff --git a/dashboard.tf b/dashboard.tf new file mode 100644 index 0000000..d4a049f --- /dev/null +++ b/dashboard.tf @@ -0,0 +1,10 @@ +module "application_dashboard" { + source = "./modules/dashboard/" + + count = length(var.application_dashboard) > 0 ? 1 : 0 + + name = var.name + rows = var.application_dashboard.rows + data_source = var.application_dashboard.data_source + variables = var.application_dashboard.variables +} diff --git a/modules/dashboard/.gitignore b/modules/dashboard/.gitignore new file mode 100644 index 0000000..45a739c --- /dev/null +++ b/modules/dashboard/.gitignore @@ -0,0 +1 @@ +*.tfstate diff --git a/modules/dashboard/README.md b/modules/dashboard/README.md new file mode 100644 index 0000000..81ee5d5 --- /dev/null +++ b/modules/dashboard/README.md @@ -0,0 +1,118 @@ +# Module to create Grafana dashboard from json/hcl +## Yaml example +``` +source: dasmeta/grafana/onpremise//modules/dashboard +version: x.y.z +variables: + name: test-dashboard + data_source: + uid: "0000" + rows: + - type : block/sla + - type : "block/ingress" + - type : "block/service" + name : "service-name-1" + host : "example.com" + - type : "block/service", + name : "service-name-2" + - + - type : "text/title", + text : "End" +``` + +## HCL example +``` +module "this" { + source = "dasmeta/grafana/onpremise//modules/dashboard" + version = "x.y.z" + + name = "test-dashboard-with-blocks" + data_source = { + uid: "0000" + } + + rows = [ + { "type" : "block/sla" }, + { type : "block/ingress" }, + { type : "block/service", name : "service-name-1", namespace: "dev", host : "example.com" }, + { type : "block/service", name : "service-name-2", namespace: "dev" }, + { type : text/title, text: "End"} + ] +} +``` + +## How add new widget +1. create module in modules/widgets (copy from one) +2. implement data loading as required +3. add new widget tf module in widget-{widget-group-name | single}.tf file +4. add new widget line in widget_result local + +## How add new block +1. create module in modules/blocks (copy from one) +2. implement data loading as required +3. add new block tf module in widget-blocks.tf +4. add new block line in blocks_results local + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [grafana](#requirement\_grafana) | >= 3.7.0 | +| [random](#requirement\_random) | >= 3.6.2 | + +## Providers + +| Name | Version | +|------|---------| +| [grafana](#provider\_grafana) | >= 3.7.0 | +| [random](#provider\_random) | >= 3.6.2 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [block\_ingress](#module\_block\_ingress) | ./modules/blocks/ingress | n/a | +| [block\_service](#module\_block\_service) | ./modules/blocks/service | n/a | +| [block\_sla](#module\_block\_sla) | ./modules/blocks/sla | n/a | +| [container\_cpu\_widget](#module\_container\_cpu\_widget) | ./modules/widgets/container/cpu | n/a | +| [container\_memory\_widget](#module\_container\_memory\_widget) | ./modules/widgets/container/memory | n/a | +| [container\_network\_widget](#module\_container\_network\_widget) | ./modules/widgets/container/network | n/a | +| [container\_replicas\_widget](#module\_container\_replicas\_widget) | ./modules/widgets/container/replicas | n/a | +| [container\_request\_count\_widget](#module\_container\_request\_count\_widget) | ./modules/widgets/container/request-count | n/a | +| [container\_response\_time\_widget](#module\_container\_response\_time\_widget) | ./modules/widgets/container/response-time | n/a | +| [container\_restarts\_widget](#module\_container\_restarts\_widget) | ./modules/widgets/container/restarts | n/a | +| [ingress\_connections\_widget](#module\_ingress\_connections\_widget) | ./modules/widgets/ingress/connections | n/a | +| [ingress\_request\_count\_widget](#module\_ingress\_request\_count\_widget) | ./modules/widgets/ingress/request-count | n/a | +| [ingress\_request\_rate\_widget](#module\_ingress\_request\_rate\_widget) | ./modules/widgets/ingress/request-rate | n/a | +| [ingress\_response\_time\_widget](#module\_ingress\_response\_time\_widget) | ./modules/widgets/ingress/response-time | n/a | +| [text\_title](#module\_text\_title) | ./modules/widgets/text/title | n/a | +| [text\_title\_with\_collapse](#module\_text\_title\_with\_collapse) | ./modules/widgets/text/title-with-collapse | n/a | +| [text\_title\_with\_link](#module\_text\_title\_with\_link) | ./modules/widgets/text/title-with-link | n/a | +| [widget\_custom](#module\_widget\_custom) | ./modules/widgets/custom | n/a | +| [widget\_sla\_slo\_sli\_latency](#module\_widget\_sla\_slo\_sli\_latency) | ./modules/widgets/sla-slo-sli/latency | n/a | +| [widget\_sla\_slo\_sli\_main](#module\_widget\_sla\_slo\_sli\_main) | ./modules/widgets/sla-slo-sli/main | n/a | + +## Resources + +| Name | Type | +|------|------| +| [grafana_dashboard.metrics](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/dashboard) | resource | +| [random_string.grafana_dashboard_id](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [data\_source](#input\_data\_source) | The grafana dashboard global/default datasource, will be used in widget items if they have no their custom ones |
object({
uid = string
type = optional(string, "prometheus")
})
| n/a | yes | +| [defaults](#input\_defaults) | Default values to be supplied to all modules. | `any` | `{}` | no | +| [name](#input\_name) | Dashboard name. Should not contain spaces and special chars. | `string` | n/a | yes | +| [rows](#input\_rows) | List of widgets to be inserted into the dashboard. See ./modules/widgets folder to see list of available widgets. | `any` | n/a | yes | +| [variables](#input\_variables) | Allows to define variables to be used in dashboard |
list(object({
name = string
type = optional(string, "custom")
hide = optional(number, 0)
includeAll = optional(bool, false)
multi = optional(bool, false)
query = optional(string, "")
queryValue = optional(string, "")
skipUrlSync = optional(bool, false)
options = optional(list(object({
selected = optional(bool, false)
value = string
text = optional(string, null)
})), [])
}
))
| `[]` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [dump](#output\_dump) | n/a | + diff --git a/modules/dashboard/locals.tf b/modules/dashboard/locals.tf new file mode 100644 index 0000000..3f1952b --- /dev/null +++ b/modules/dashboard/locals.tf @@ -0,0 +1,134 @@ +locals { + dashboard_title = var.name + + # fill dashboard variable options and current required fields + grafana_templating_list_variables_options_fill = [for variable in var.variables : merge(variable, { + options = [for option in variable.options : merge(option, { text = coalesce(option.text, option.value) })] + })] + grafana_templating_list_variables = [for variable in local.grafana_templating_list_variables_options_fill : merge(variable, { + current = try(variable.options[index(variable.options.*.selected, true)], null) + })] + + + ## Blocks + + # get all blocks and annotate + initial_blocks = [ + for index1, block in var.rows : { + block : block, + index1 : index1, + type : replace(block.type, "block/", "") + } if strcontains(try(block.type, ""), "block/") + ] + + # annotate each block type with subIndex + blocks_by_type = { for block_type in distinct([for block in local.initial_blocks : block.type]) : + block_type => [for index2, block in local.initial_blocks : merge(block, { index2 : index2 }) if strcontains(block.type, block_type)] + } + + # bring all module results together + blocks_results = { + ingress = values(module.block_ingress).*.result + service = values(module.block_service).*.result + sla = values(module.block_sla).*.result + } + + blocks_by_type_results = concat([], [ + for block_type, type_blocks in local.blocks_by_type : [ + for index3, block in type_blocks : merge(block, { results : local.blocks_results[block.type][index3] }) if contains(keys(local.blocks_results), block.type) + ] if contains(keys(local.blocks_results), block_type) + ]...) + + # inject block widgets into rows/panels listing in place of block/* definitions + rows = concat([], [ + for index1, row in var.rows : + concat(strcontains(try(row.type, ""), "block/") ? [] : [row], + [ + for item in local.blocks_by_type_results : item.results if item.index1 == index1 + ]...) + ]...) + + + ## Widgets + # default values from module and provided from outside + widget_default_values = merge( + { + period = 3 # in minutes + stat = "Sum" + width = 6 + height = 8 + expressions = [] + yAxis = { left = { min = 0 } } + data_source = var.data_source + container = "$container" + namespace = "$namespace" + cluster = "$cluster" + account_id = null + region = null + anomaly_detection = false + anomaly_deviation = 6 + }, + var.defaults + ) + + # this will walk through every widget and add row/column + merge with default values + widget_config_with_raw_column_data_and_defaults = [ + for row_number, row in local.rows : [ + for column_number, column in row : merge( + local.widget_default_values, + column, + { + row = row_number, + column = column_number, + row_count = length(local.rows), + column_count = length(row) + } + ) + ] + ] + + # groups rows by widget type + widget_config = { for key, item in flatten(local.widget_config_with_raw_column_data_and_defaults) : + item.type => merge( + item, + # calculate coordinates based on defaults and row/column details + { + coordinates = { + x = item.column * item.width + y = item.row + width = item.width + height = item.height + } + } + )... } + + # combine results (last step) + widget_result = concat( + # Container widgets + values(module.container_cpu_widget).*.data, + values(module.container_memory_widget).*.data, + values(module.container_network_widget).*.data, + values(module.container_restarts_widget).*.data, + values(module.container_replicas_widget).*.data, + values(module.container_request_count_widget).*.data, + values(module.container_response_time_widget).*.data, + + # Ingress widgets + values(module.ingress_connections_widget).*.data, + values(module.ingress_request_rate_widget).*.data, + values(module.ingress_request_count_widget).*.data, + values(module.ingress_response_time_widget).*.data, + + # Text widgets + values(module.text_title).*.data, + values(module.text_title_with_link).*.data, + values(module.text_title_with_collapse).*.data, + + # sla/slo/sli widgets + values(module.widget_sla_slo_sli_main).*.data, + values(module.widget_sla_slo_sli_latency).*.data, + + # single widgets + values(module.widget_custom).*.data, + ) +} diff --git a/modules/dashboard/main.tf b/modules/dashboard/main.tf new file mode 100644 index 0000000..d5644d8 --- /dev/null +++ b/modules/dashboard/main.tf @@ -0,0 +1,32 @@ +resource "grafana_dashboard" "metrics" { + config_json = jsonencode({ + uid = random_string.grafana_dashboard_id.result + title = local.dashboard_title + style = "dark" + timezone = "browser" + editable = true + schemaVersion = 35 + fiscalYearStartMonth = 0 + graphTooltip = 0 + links = [] + liveNow = false + annotations = {} + refresh = "1m" + tags = [] + templating = { + list = local.grafana_templating_list_variables + } + time = { + from = "now-6h" + to = "now" + } + timepicker = {} + weekStart = "" + panels = local.widget_result + }) +} + +resource "random_string" "grafana_dashboard_id" { + length = 16 + special = false +} diff --git a/modules/dashboard/modules/blocks/ingress/README.md b/modules/dashboard/modules/blocks/ingress/README.md new file mode 100644 index 0000000..dc0f45e --- /dev/null +++ b/modules/dashboard/modules/blocks/ingress/README.md @@ -0,0 +1,33 @@ +# ingress + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | AWS account ID | `string` | n/a | yes | +| [balancer\_name](#input\_balancer\_name) | ALB name | `string` | n/a | yes | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [result](#output\_result) | description | + diff --git a/modules/dashboard/modules/blocks/ingress/output.tf b/modules/dashboard/modules/blocks/ingress/output.tf new file mode 100644 index 0000000..37dd137 --- /dev/null +++ b/modules/dashboard/modules/blocks/ingress/output.tf @@ -0,0 +1,24 @@ +output "result" { + description = "description" + value = [ + [ + { type : "text/title-with-collapse", text : "Nginx Ingress Controller" } + ], + [ + { type : "ingress/request-rate" }, + { type : "ingress/connections" }, + { type : "ingress/response-time" }, + { type : "ingress/request-count" } + ], + [ + { type : "ingress/request-rate", by_host : true }, + { type : "ingress/response-time", by_host : true }, + { type : "ingress/request-count", by_host : true }, + { type : "ingress/request-count", by_host : true, only_5xx : true } + ], + [ + { type : "container/cpu", container : "controller", namespace : "ingress-nginx", width : 12 }, + { type : "container/memory", container : "controller", namespace : "ingress-nginx", width : 12 } + ], + ] +} diff --git a/modules/dashboard/modules/blocks/ingress/variables.tf b/modules/dashboard/modules/blocks/ingress/variables.tf new file mode 100644 index 0000000..cee789b --- /dev/null +++ b/modules/dashboard/modules/blocks/ingress/variables.tf @@ -0,0 +1,14 @@ +variable "account_id" { + type = string + description = "AWS account ID" +} + +variable "balancer_name" { + type = string + description = "ALB name" +} + +variable "region" { + type = string + default = "" +} diff --git a/modules/dashboard/modules/blocks/service/README.md b/modules/dashboard/modules/blocks/service/README.md new file mode 100644 index 0000000..681b510 --- /dev/null +++ b/modules/dashboard/modules/blocks/service/README.md @@ -0,0 +1,40 @@ +# service + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [balancer\_name](#input\_balancer\_name) | ALB name | `string` | n/a | yes | +| [cluster](#input\_cluster) | EKS cluster name | `string` | n/a | yes | +| [healthcheck\_id](#input\_healthcheck\_id) | R53 healthcheck ID for the service | `string` | n/a | yes | +| [host](#input\_host) | The service host name | `string` | `null` | no | +| [log\_group\_name](#input\_log\_group\_name) | The log group name where app sends logs | `string` | n/a | yes | +| [name](#input\_name) | Service nameD | `string` | n/a | yes | +| [namespace](#input\_namespace) | EKS namespace name | `string` | n/a | yes | +| [region](#input\_region) | n/a | `string` | `""` | no | +| [target\_group\_arn](#input\_target\_group\_arn) | Target group ARN which points to the service | `string` | n/a | yes | +| [version\_label](#input\_version\_label) | The deployment label which shows app version | `string` | `"app-version"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [result](#output\_result) | description | + diff --git a/modules/dashboard/modules/blocks/service/output.tf b/modules/dashboard/modules/blocks/service/output.tf new file mode 100644 index 0000000..077ff51 --- /dev/null +++ b/modules/dashboard/modules/blocks/service/output.tf @@ -0,0 +1,27 @@ +output "result" { + description = "description" + value = [ + [ + { type : "text/title-with-collapse", text : var.name } + ], + concat( + var.host != null ? [ + { type : "container/request-count", host : var.host, container : var.name, cluster : var.cluster, namespace : var.namespace }, + { type : "container/request-count", host : var.host, container : var.name, cluster : var.cluster, namespace : var.namespace, only_5xx : true }, + { type : "container/response-time", host : var.host, container : var.name, cluster : var.cluster, namespace : var.namespace }, + ] : [], + [ + { type : "container/network", host : var.host, container : var.name, cluster : var.cluster, namespace : var.namespace, width : var.host != null ? 6 : 24, anomaly_detection : true }, + ] + ), + [ + { type : "container/cpu", container : var.name, cluster : var.cluster, namespace : var.namespace, anomaly_detection : true }, + { type : "container/memory", container : var.name, cluster : var.cluster, namespace : var.namespace, anomaly_detection : true }, + { type : "container/replicas", container : var.name, cluster : var.cluster, namespace : var.namespace }, + { type : "container/restarts", container : var.name, cluster : var.cluster, namespace : var.namespace }, + ], + [ + { type : "container/cpu", container : var.name, cluster : var.cluster, namespace : var.namespace, anomaly_detection : true, by_pod : true, width : 24 }, + ], + ] +} diff --git a/modules/dashboard/modules/blocks/service/variables.tf b/modules/dashboard/modules/blocks/service/variables.tf new file mode 100644 index 0000000..9d9075f --- /dev/null +++ b/modules/dashboard/modules/blocks/service/variables.tf @@ -0,0 +1,51 @@ +variable "name" { + type = string + description = "Service nameD" +} + +variable "balancer_name" { + type = string + description = "ALB name" +} + +variable "target_group_arn" { + type = string + description = "Target group ARN which points to the service" +} + +variable "healthcheck_id" { + type = string + description = "R53 healthcheck ID for the service" +} + +variable "cluster" { + type = string + description = "EKS cluster name" +} + +variable "namespace" { + type = string + description = "EKS namespace name" +} + +variable "region" { + type = string + default = "" +} + +variable "version_label" { + type = string + description = "The deployment label which shows app version" + default = "app-version" +} + +variable "log_group_name" { + type = string + description = "The log group name where app sends logs" +} + +variable "host" { + type = string + default = null + description = "The service host name" +} diff --git a/modules/dashboard/modules/blocks/sla/README.md b/modules/dashboard/modules/blocks/sla/README.md new file mode 100644 index 0000000..330f6b3 --- /dev/null +++ b/modules/dashboard/modules/blocks/sla/README.md @@ -0,0 +1,32 @@ +# sla + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [balancer\_name](#input\_balancer\_name) | ALB name | `string` | n/a | yes | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [result](#output\_result) | description | + diff --git a/modules/dashboard/modules/blocks/sla/output.tf b/modules/dashboard/modules/blocks/sla/output.tf new file mode 100644 index 0000000..3ff85de --- /dev/null +++ b/modules/dashboard/modules/blocks/sla/output.tf @@ -0,0 +1,10 @@ +output "result" { + description = "description" + value = [ + [ + { type = "sla-slo-sli/main", width : 5, height : 6, balancer_name = var.balancer_name }, + { type = "sla-slo-sli/latency", width : 5, height : 6, balancer_name = var.balancer_name }, + { type = "sla-slo-sli/latency", width : 14, height : 6, balancer_name = var.balancer_name, histogram : true } + ], + ] +} diff --git a/modules/dashboard/modules/blocks/sla/variables.tf b/modules/dashboard/modules/blocks/sla/variables.tf new file mode 100644 index 0000000..316ad95 --- /dev/null +++ b/modules/dashboard/modules/blocks/sla/variables.tf @@ -0,0 +1,9 @@ +variable "balancer_name" { + type = string + description = "ALB name" +} + +variable "region" { + type = string + default = "" +} diff --git a/modules/dashboard/modules/widgets/base/README.md b/modules/dashboard/modules/widgets/base/README.md new file mode 100644 index 0000000..f64f848 --- /dev/null +++ b/modules/dashboard/modules/widgets/base/README.md @@ -0,0 +1,60 @@ +# base + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base\_grafana](#module\_base\_grafana) | ./platforms/grafana | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alarms](#input\_alarms) | The list of alarm\_arns used for properties->alarms option in alarm widgets | `list(string)` | `null` | no | +| [annotations](#input\_annotations) | The annotations option for alarm widgets | `any` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [coordinates](#input\_coordinates) | n/a |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [decimals](#input\_decimals) | The decimals to enable on numbers | `number` | `0` | no | +| [defaults](#input\_defaults) | Default values that will be passed to all metrics. | `any` | `{}` | no | +| [end](#input\_end) | end of widget | `string` | `null` | no | +| [expressions](#input\_expressions) | Custom metric expressions over metrics, note that metrics have auto generated m1,m2,..., m{n} ids |
list(object({
expression = string
label = optional(string, null)
accountId = optional(string, null)
visible = optional(bool, null)
color = optional(string, null)
yAxis = optional(string, null)
region = optional(string, null)
id = optional(string, null)
}))
| `[]` | no | +| [fillOpacity](#input\_fillOpacity) | The fillOpacity value | `number` | `0` | no | +| [metrics](#input\_metrics) | Metrics to be displayed on the widget. | `any` | `[]` | no | +| [name](#input\_name) | n/a | `string` | n/a | yes | +| [period](#input\_period) | n/a | `number` | `3` | no | +| [properties\_type](#input\_properties\_type) | The properties->type option for alarm widgets | `string` | `null` | no | +| [query](#input\_query) | The Logs Insights complete build query without sources and other options(in case of metric) query | `string` | `null` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | +| [setPeriodToTimeRange](#input\_setPeriodToTimeRange) | setPeriodToTimeRange of widget | `bool` | `null` | no | +| [singleValueFullPrecision](#input\_singleValueFullPrecision) | singleValueFullPrecision of widget | `bool` | `null` | no | +| [sources](#input\_sources) | Log groups list for Logs Insights query | `list(string)` | `[]` | no | +| [sparkline](#input\_sparkline) | sparkline of widget | `bool` | `null` | no | +| [stacked](#input\_stacked) | The stacked option for log insights and alarm widgets | `bool` | `null` | no | +| [start](#input\_start) | start of widget | `string` | `null` | no | +| [stat](#input\_stat) | n/a | `string` | `"Average"` | no | +| [trend](#input\_trend) | trend of widget | `bool` | `null` | no | +| [type](#input\_type) | The type of widget to be prepared | `string` | `"metric"` | no | +| [view](#input\_view) | The view for log insights and alarm widgets | `string` | `null` | no | +| [yAxis](#input\_yAxis) | Widget Item common yAxis option (applied only metric type widgets). | `any` |
{
"left": {}
}
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/base/account.tf b/modules/dashboard/modules/widgets/base/account.tf new file mode 100644 index 0000000..e69de29 diff --git a/modules/dashboard/modules/widgets/base/main.tf b/modules/dashboard/modules/widgets/base/main.tf new file mode 100644 index 0000000..4233a89 --- /dev/null +++ b/modules/dashboard/modules/widgets/base/main.tf @@ -0,0 +1,23 @@ +module "base_grafana" { + source = "./platforms/grafana" + + name = var.name + data_source = var.data_source + coordinates = var.coordinates + metrics = var.metrics + defaults = var.defaults + stat = var.stat + period = var.period + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + type = var.type + query = var.query + sources = var.sources + view = var.view + stacked = var.stacked + annotations = var.annotations + alarms = var.alarms + properties_type = var.properties_type + decimals = var.decimals + fillOpacity = var.fillOpacity +} diff --git a/modules/dashboard/modules/widgets/base/outputs.tf b/modules/dashboard/modules/widgets/base/outputs.tf new file mode 100644 index 0000000..f83a9ae --- /dev/null +++ b/modules/dashboard/modules/widgets/base/outputs.tf @@ -0,0 +1,6 @@ +output "data" { + value = try( + module.base_grafana.data, + null + ) +} diff --git a/modules/dashboard/modules/widgets/base/platforms/grafana/README.md b/modules/dashboard/modules/widgets/base/platforms/grafana/README.md new file mode 100644 index 0000000..2b068ad --- /dev/null +++ b/modules/dashboard/modules/widgets/base/platforms/grafana/README.md @@ -0,0 +1,49 @@ +# grafana + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [alarms](#input\_alarms) | The list of alarm\_arns used for properties->alarms option in alarm widgets | `list(string)` | `null` | no | +| [annotations](#input\_annotations) | The annotations option for alarm widgets | `any` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Height of anomaly band | `number` | `4` | no | +| [coordinates](#input\_coordinates) | n/a |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [decimals](#input\_decimals) | The decimals to enable on numbers | `number` | `0` | no | +| [defaults](#input\_defaults) | Default values that will be passed to all metrics. | `any` | `{}` | no | +| [fillOpacity](#input\_fillOpacity) | The fillOpacity value | `number` | `0` | no | +| [metrics](#input\_metrics) | Metrics to be displayed on the widget. | `any` | `[]` | no | +| [name](#input\_name) | n/a | `string` | n/a | yes | +| [period](#input\_period) | n/a | `number` | `3` | no | +| [properties\_type](#input\_properties\_type) | The properties->type option for alarm widgets | `string` | `null` | no | +| [query](#input\_query) | The Logs Insights complete build query without sources and other options(in case of metric) query | `string` | `null` | no | +| [sources](#input\_sources) | Log groups list for Logs Insights query | `list(string)` | `[]` | no | +| [stacked](#input\_stacked) | The stacked option for log insights and alarm widgets | `bool` | `null` | no | +| [stat](#input\_stat) | n/a | `string` | `"Average"` | no | +| [type](#input\_type) | The type of widget to be prepared | `string` | `"metric"` | no | +| [view](#input\_view) | The view for log insights and alarm widgets | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/base/platforms/grafana/outputs.tf b/modules/dashboard/modules/widgets/base/platforms/grafana/outputs.tf new file mode 100644 index 0000000..b140c6e --- /dev/null +++ b/modules/dashboard/modules/widgets/base/platforms/grafana/outputs.tf @@ -0,0 +1,3 @@ +output "data" { + value = local.data +} diff --git a/modules/dashboard/modules/widgets/base/platforms/grafana/variables.tf b/modules/dashboard/modules/widgets/base/platforms/grafana/variables.tf new file mode 100644 index 0000000..15d27db --- /dev/null +++ b/modules/dashboard/modules/widgets/base/platforms/grafana/variables.tf @@ -0,0 +1,119 @@ +variable "name" { + type = string +} + +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +variable "metrics" { + type = any + default = [] + description = "Metrics to be displayed on the widget." +} + +variable "defaults" { + type = any + default = {} + description = "Default values that will be passed to all metrics." +} + +variable "stat" { + type = string + default = "Average" +} + +variable "period" { + type = number + default = 3 +} + +# variable "region" { +# type = string +# default = "" +# } + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 4 + description = "Height of anomaly band" +} + +variable "type" { + type = string + default = "metric" + description = "The type of widget to be prepared" +} + +variable "query" { + type = string + default = null + description = "The Logs Insights complete build query without sources and other options(in case of metric) query" +} + +variable "sources" { + type = list(string) + default = [] + description = "Log groups list for Logs Insights query" +} + +variable "view" { + type = string + default = null + description = "The view for log insights and alarm widgets" +} + +variable "stacked" { + type = bool + default = null + description = "The stacked option for log insights and alarm widgets" +} + +variable "annotations" { + type = any + default = null + description = "The annotations option for alarm widgets" +} + +variable "alarms" { + type = list(string) + default = null + description = "The list of alarm_arns used for properties->alarms option in alarm widgets" +} + +variable "properties_type" { + type = string + default = null + description = "The properties->type option for alarm widgets" +} + +variable "decimals" { + type = number + default = 0 + description = "The decimals to enable on numbers" +} + +variable "fillOpacity" { + type = number + default = 0 + description = "The fillOpacity value" +} diff --git a/modules/dashboard/modules/widgets/base/platforms/grafana/widget.tf b/modules/dashboard/modules/widgets/base/platforms/grafana/widget.tf new file mode 100644 index 0000000..74ee9ae --- /dev/null +++ b/modules/dashboard/modules/widgets/base/platforms/grafana/widget.tf @@ -0,0 +1,124 @@ +locals { + field_config_defaults = { + "color" : { + "mode" : "palette-classic" + }, + decimals : var.decimals, + "custom" : { + "axisLabel" : "", + "axisPlacement" : "auto", + "barAlignment" : 0, + "drawStyle" : "line", + "fillOpacity" : var.fillOpacity, + "gradientMode" : "none", + "hideFrom" : { + "legend" : false, + "tooltip" : false, + "viz" : false + }, + "lineInterpolation" : "linear", + "lineWidth" : 1, + "pointSize" : 5, + "scaleDistribution" : { + "type" : "linear" + }, + "showPoints" : "auto", + "spanNulls" : false, + "stacking" : { + "group" : "A", + "mode" : "none" + }, + "thresholdsStyle" : { + "mode" : "off" + } + }, + mappings = [] + thresholds = { + mode = "absolute" + steps = [ + { + color = "green" + value = null + }, + { + color = "red" + value = 80 + }, + ] + } + } + + common_fields = ["MetricNamespace", "MetricName"] + attribute_fields = ["accountId", "period", "stat", "label", "visible", "color", "yAxis"] + custom_fields = ["anomaly_detection", "anomaly_deviation"] + metrics_local = var.metrics == null ? [] : var.metrics + + # merge metrics with defaults + metrics_with_defaults = [for metric in local.metrics_local : merge(var.defaults, metric)] + + type_map = { + timeSeries = "timeseries" + gauge = "gauge" + histogram = "histogram" + } + + # create query and metric based targets + query_targets = var.query != null ? [ + { + expression = var.query, + logGroupNames = var.sources, + queryMode = "Logs", + legendFormat = "" + # region = var.region + } + ] : [] + metric_targets = [for row in local.metrics_with_defaults : row.expression != null ? { + expr = row.expression + id = "" + legendFormat = row.label, + editorMode = "code", + + } : { + expression = "" + id = "" + matchExact = true + metricEditorMode = 0 + metricName = row.MetricName + metricQueryType = 0 + namespace = row.MetricNamespace + period = "" + queryMode = "Metrics" + # region = var.region + sqlExpression = "" + statistic = "Average" + dimensions = { for key, item in row : key => item if !contains(concat(local.common_fields, local.attribute_fields, local.custom_fields), key) } + }] + + data = { + datasource = var.data_source + fieldConfig = { + defaults = local.field_config_defaults + overrides = [] + } + gridPos = { + h = var.coordinates.height + w = var.coordinates.width + x = var.coordinates.x + y = var.coordinates.y + } + title = var.name + type = try(local.type_map[var.view], local.type_map.timeSeries) + options = { + legend = { + calcs = [] + displayMode = "list" + placement = "bottom" + } + tooltip = { + mode = "single" + sort = "none" + } + } + targets = concat(local.query_targets, local.metric_targets) + } +} diff --git a/modules/dashboard/modules/widgets/base/variables.tf b/modules/dashboard/modules/widgets/base/variables.tf new file mode 100644 index 0000000..7128400 --- /dev/null +++ b/modules/dashboard/modules/widgets/base/variables.tf @@ -0,0 +1,176 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "name" { + type = string +} + +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +variable "metrics" { + type = any + default = [] + description = "Metrics to be displayed on the widget." +} + +variable "expressions" { + type = list(object({ + expression = string + label = optional(string, null) + accountId = optional(string, null) + visible = optional(bool, null) + color = optional(string, null) + yAxis = optional(string, null) + region = optional(string, null) + id = optional(string, null) + })) + default = [] + description = "Custom metric expressions over metrics, note that metrics have auto generated m1,m2,..., m{n} ids" +} + +variable "defaults" { + type = any + default = {} + description = "Default values that will be passed to all metrics." +} + +variable "stat" { + type = string + default = "Average" +} + +variable "period" { + type = number + default = 3 +} + +variable "region" { + type = string + default = "" +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} + +variable "type" { + type = string + default = "metric" + description = "The type of widget to be prepared" +} + +variable "query" { + type = string + default = null + description = "The Logs Insights complete build query without sources and other options(in case of metric) query" +} + +variable "sources" { + type = list(string) + default = [] + description = "Log groups list for Logs Insights query" +} + +variable "view" { + type = string + default = null + description = "The view for log insights and alarm widgets" +} + +variable "stacked" { + type = bool + default = null + description = "The stacked option for log insights and alarm widgets" +} + +variable "annotations" { + type = any + default = null + description = "The annotations option for alarm widgets" +} + +variable "alarms" { + type = list(string) + default = null + description = "The list of alarm_arns used for properties->alarms option in alarm widgets" +} + +variable "properties_type" { + type = string + default = null + description = "The properties->type option for alarm widgets" +} + +variable "yAxis" { + type = any + default = { left = {} } + description = "Widget Item common yAxis option (applied only metric type widgets)." +} + +variable "setPeriodToTimeRange" { + type = bool + default = null + description = "setPeriodToTimeRange of widget" +} + +variable "singleValueFullPrecision" { + type = bool + default = null + description = "singleValueFullPrecision of widget" +} + +variable "sparkline" { + type = bool + default = null + description = "sparkline of widget" +} + +variable "trend" { + type = bool + default = null + description = "trend of widget" +} + +variable "start" { + type = string + default = null + description = "start of widget" +} + +variable "end" { + type = string + default = null + description = "end of widget" +} + +variable "decimals" { + type = number + default = 0 + description = "The decimals to enable on numbers" +} + +variable "fillOpacity" { + type = number + default = 0 + description = "The fillOpacity value" +} diff --git a/modules/dashboard/modules/widgets/container/README.md b/modules/dashboard/modules/widgets/container/README.md new file mode 100644 index 0000000..d236723 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/README.md @@ -0,0 +1,25 @@ + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/modules/dashboard/modules/widgets/container/cpu/README.md b/modules/dashboard/modules/widgets/container/cpu/README.md new file mode 100644 index 0000000..3ea24b7 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/cpu/README.md @@ -0,0 +1,43 @@ +# cpu + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [by\_pod](#input\_by\_pod) | n/a | `bool` | `false` | no | +| [cluster](#input\_cluster) | n/a | `string` | n/a | yes | +| [container](#input\_container) | n/a | `string` | n/a | yes | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [namespace](#input\_namespace) | n/a | `string` | `"default"` | no | +| [period](#input\_period) | stats | `number` | `60` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/container/cpu/base.tf b/modules/dashboard/modules/widgets/container/cpu/base.tf new file mode 100644 index 0000000..a4995de --- /dev/null +++ b/modules/dashboard/modules/widgets/container/cpu/base.tf @@ -0,0 +1,30 @@ +module "base" { + source = "../../base" + + name = "CPU [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + ClusterName = var.cluster + Namespace = var.namespace + PodName = var.container + } + + metrics = var.by_pod ? [ + { label = "__auto", expression = "avg(rate(container_cpu_usage_seconds_total{container=\"${var.container}\", namespace=\"${var.namespace}\"}[${var.period}m])) by (pod)" }, + { label = "__auto", expression = "max(rate(container_cpu_usage_seconds_total{container=\"${var.container}\", namespace=\"${var.namespace}\"}[${var.period}m])) by (pod)" }, + { label = "Request", expression = "avg(kube_pod_container_resource_requests{container=\"${var.container}\", namespace=\"${var.namespace}\", resource=\"cpu\"})" }, + { label = "Limit", expression = "avg(kube_pod_container_resource_limits{container=\"${var.container}\", namespace=\"${var.namespace}\", resource=\"cpu\"})" }, + ] : [ + { label = "AVG", expression = "avg(rate(container_cpu_usage_seconds_total{container=\"${var.container}\", namespace=\"${var.namespace}\"}[${var.period}m])) by (container)" }, + { label = "Request", expression = "avg(kube_pod_container_resource_requests{container=\"${var.container}\", namespace=\"${var.namespace}\", resource=\"cpu\"})" }, + { label = "Limit", expression = "avg(kube_pod_container_resource_limits{container=\"${var.container}\", namespace=\"${var.namespace}\", resource=\"cpu\"})" }, + { label = "MAX", expression = "max(rate(container_cpu_usage_seconds_total{container=\"${var.container}\", namespace=\"${var.namespace}\"}[${var.period}m])) by (container)" }, + ] +} diff --git a/modules/dashboard/modules/widgets/container/cpu/output.tf b/modules/dashboard/modules/widgets/container/cpu/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/container/cpu/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/container/cpu/variables.tf b/modules/dashboard/modules/widgets/container/cpu/variables.tf new file mode 100644 index 0000000..e966e90 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/cpu/variables.tf @@ -0,0 +1,63 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "container" { + type = string +} + +variable "cluster" { + type = string +} + +variable "namespace" { + type = string + default = "default" +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 60 +} + +variable "by_pod" { + type = bool + default = false +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/container/memory/README.md b/modules/dashboard/modules/widgets/container/memory/README.md new file mode 100644 index 0000000..02e2f32 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/memory/README.md @@ -0,0 +1,42 @@ +# memory + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [cluster](#input\_cluster) | n/a | `string` | n/a | yes | +| [container](#input\_container) | n/a | `string` | n/a | yes | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [namespace](#input\_namespace) | n/a | `string` | `"default"` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/container/memory/base.tf b/modules/dashboard/modules/widgets/container/memory/base.tf new file mode 100644 index 0000000..0296969 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/memory/base.tf @@ -0,0 +1,26 @@ +module "base" { + source = "../../base" + + name = "Memory" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + ClusterName = var.cluster + Namespace = var.namespace + PodName = var.container + accountId = var.account_id + } + + metrics = [ + { label = "AVG", expression = "avg(container_memory_usage_bytes{container=\"${var.container}\", namespace=\"${var.namespace}\"}) by (container) / 1024 / 1024" }, + { label = "Request", expression = "avg(kube_pod_container_resource_requests{container=\"${var.container}\", namespace=\"${var.namespace}\", resource=\"memory\"}) / 1024 / 1024" }, + { label = "Limit", expression = "avg(kube_pod_container_resource_limits{container=\"${var.container}\", namespace=\"${var.namespace}\", resource=\"memory\"}) / 1024 / 1024" }, + { label = "MAX", expression = "max(container_memory_usage_bytes{container=\"${var.container}\", namespace=\"${var.namespace}\"}) by (container) / 1024 / 1024" }, + ] +} diff --git a/modules/dashboard/modules/widgets/container/memory/output.tf b/modules/dashboard/modules/widgets/container/memory/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/container/memory/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/container/memory/variables.tf b/modules/dashboard/modules/widgets/container/memory/variables.tf new file mode 100644 index 0000000..c8067bd --- /dev/null +++ b/modules/dashboard/modules/widgets/container/memory/variables.tf @@ -0,0 +1,58 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "container" { + type = string +} + +variable "cluster" { + type = string +} + +variable "namespace" { + type = string + default = "default" +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/container/network/README.md b/modules/dashboard/modules/widgets/container/network/README.md new file mode 100644 index 0000000..9629faf --- /dev/null +++ b/modules/dashboard/modules/widgets/container/network/README.md @@ -0,0 +1,43 @@ +# network + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [cluster](#input\_cluster) | n/a | `string` | n/a | yes | +| [container](#input\_container) | n/a | `string` | n/a | yes | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [host](#input\_host) | n/a | `string` | `null` | no | +| [namespace](#input\_namespace) | n/a | `string` | `"default"` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/container/network/base.tf b/modules/dashboard/modules/widgets/container/network/base.tf new file mode 100644 index 0000000..2949a69 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/network/base.tf @@ -0,0 +1,27 @@ +module "base" { + source = "../../base" + + name = "Network In/Out [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + ClusterName = var.cluster + Namespace = var.namespace + PodName = var.container + accountId = var.account_id + } + + + metrics = concat([ + { label = "In(container)", expression = "sum(rate(container_network_receive_bytes_total{namespace=\"${var.namespace}\", pod=~\"${var.container}-.*\"} [${var.period}m])) / 100000" }, + { label = "Out(container)", expression = "sum(rate(container_network_transmit_bytes_total{namespace=\"${var.namespace}\", pod=~\"${var.container}-.*\"} [${var.period}m])) / 100000" }, + ], + var.host != null ? [{ label = "In(ingress)", expression = "sum(rate(nginx_ingress_controller_bytes_sent_bucket{host=\"${var.host}\"}[${var.period}m]))" }] : [], + ) +} diff --git a/modules/dashboard/modules/widgets/container/network/output.tf b/modules/dashboard/modules/widgets/container/network/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/container/network/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/container/network/variables.tf b/modules/dashboard/modules/widgets/container/network/variables.tf new file mode 100644 index 0000000..81dac0c --- /dev/null +++ b/modules/dashboard/modules/widgets/container/network/variables.tf @@ -0,0 +1,63 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "container" { + type = string +} + +variable "cluster" { + type = string +} + +variable "namespace" { + type = string + default = "default" +} + +variable "host" { + type = string + default = null +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/container/replicas/README.md b/modules/dashboard/modules/widgets/container/replicas/README.md new file mode 100644 index 0000000..9f22603 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/replicas/README.md @@ -0,0 +1,42 @@ +# replicas + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [cluster](#input\_cluster) | n/a | `string` | n/a | yes | +| [container](#input\_container) | n/a | `string` | n/a | yes | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [namespace](#input\_namespace) | n/a | `string` | `"default"` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/container/replicas/base.tf b/modules/dashboard/modules/widgets/container/replicas/base.tf new file mode 100644 index 0000000..ac7d499 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/replicas/base.tf @@ -0,0 +1,26 @@ +module "base" { + source = "../../base" + + name = "Replicas" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + ClusterName = var.cluster + Namespace = var.namespace + PodName = var.container + accountId = var.account_id + } + + metrics = [ + { label = "Total", expression = "sum(kube_pod_status_phase{namespace=\"${var.namespace}\", pod=~\"${var.container}-.*\"})" }, + { label = "Running", expression = "sum(kube_pod_container_status_running{namespace=\"${var.namespace}\", pod=~\"${var.container}-.*\"})" }, + { label = "Terminated", expression = "sum(kube_pod_container_status_terminated{namespace=\"${var.namespace}\", pod=~\"${var.container}-.*\"})" }, + { label = "Waiting", expression = "sum(kube_pod_container_status_waiting{namespace=\"${var.namespace}\", pod=~\"${var.container}-.*\"})" }, + ] +} diff --git a/modules/dashboard/modules/widgets/container/replicas/output.tf b/modules/dashboard/modules/widgets/container/replicas/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/container/replicas/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/container/replicas/variables.tf b/modules/dashboard/modules/widgets/container/replicas/variables.tf new file mode 100644 index 0000000..c8067bd --- /dev/null +++ b/modules/dashboard/modules/widgets/container/replicas/variables.tf @@ -0,0 +1,58 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "container" { + type = string +} + +variable "cluster" { + type = string +} + +variable "namespace" { + type = string + default = "default" +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/container/request-count/README.md b/modules/dashboard/modules/widgets/container/request-count/README.md new file mode 100644 index 0000000..12529eb --- /dev/null +++ b/modules/dashboard/modules/widgets/container/request-count/README.md @@ -0,0 +1,42 @@ +# request-count + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [host](#input\_host) | n/a | `string` | `null` | no | +| [only\_5xx](#input\_only\_5xx) | n/a | `bool` | `false` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | +| [target\_group\_name](#input\_target\_group\_name) | n/a | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/container/request-count/base.tf b/modules/dashboard/modules/widgets/container/request-count/base.tf new file mode 100644 index 0000000..f2ef835 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/request-count/base.tf @@ -0,0 +1,33 @@ +module "base" { + source = "../../base" + + name = "Request Count ${var.only_5xx ? "5XX" : "All"} ${var.host} [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "AWS/ApplicationELB" + accountId = var.account_id + TargetGroup = var.target_group_name + } + + metrics = concat( + var.only_5xx ? [] : [ + { label = "Total", expression = "sum(rate(nginx_ingress_controller_requests{host=\"${var.host}\"}[${var.period}m]))" }, + { label = "2xx", expression = "sum(rate(nginx_ingress_controller_requests{status=~\"2..\", host=\"${var.host}\"}[${var.period}m]))" }, + { label = "3xx", expression = "sum(rate(nginx_ingress_controller_requests{status=~\"3..\", host=\"${var.host}\"}[${var.period}m]))" }, + { label = "4xx", expression = "sum(rate(nginx_ingress_controller_requests{status=~\"4..\", host=\"${var.host}\"}[${var.period}m]))" }, + + ], + [ + { label = "499", expression = "sum(rate(nginx_ingress_controller_requests{status=\"499\", host=\"${var.host}\"}[${var.period}m]))" }, + { label = "5XX", expression = "sum(rate(nginx_ingress_controller_requests{status=~\"5..\", host=\"${var.host}\"}[${var.period}m]))" }, + { label = "500", expression = "sum(rate(nginx_ingress_controller_requests{status=\"500\", host=\"${var.host}\"}[${var.period}m]))" }, + { label = "502", expression = "sum(rate(nginx_ingress_controller_requests{status=\"502\", host=\"${var.host}\"}[${var.period}m]))" }, + { label = "503", expression = "sum(rate(nginx_ingress_controller_requests{status=\"503\", host=\"${var.host}\"}[${var.period}m]))" }, + ]) +} diff --git a/modules/dashboard/modules/widgets/container/request-count/output.tf b/modules/dashboard/modules/widgets/container/request-count/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/container/request-count/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/container/request-count/variables.tf b/modules/dashboard/modules/widgets/container/request-count/variables.tf new file mode 100644 index 0000000..8e772d3 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/request-count/variables.tf @@ -0,0 +1,60 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "host" { + type = string + default = null +} + +variable "only_5xx" { + type = bool + default = false +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +variable "target_group_name" { + type = string + default = null +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/container/response-time/README.md b/modules/dashboard/modules/widgets/container/response-time/README.md new file mode 100644 index 0000000..426ded3 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/response-time/README.md @@ -0,0 +1,44 @@ +# response-time + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [acceptable](#input\_acceptable) | The number which indicates the acceptable timeout | `number` | `1` | no | +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [balancer\_name](#input\_balancer\_name) | n/a | `string` | `null` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [host](#input\_host) | n/a | `string` | `null` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [problem](#input\_problem) | The number which indicates the max timeout above which we have problem | `number` | `2` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | +| [target\_group\_name](#input\_target\_group\_name) | n/a | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/container/response-time/base.tf b/modules/dashboard/modules/widgets/container/response-time/base.tf new file mode 100644 index 0000000..0b212fc --- /dev/null +++ b/modules/dashboard/modules/widgets/container/response-time/base.tf @@ -0,0 +1,25 @@ +module "base" { + source = "../../base" + + name = "Response Time [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "AWS/ApplicationELB" + LoadBalancer = var.balancer_name + TargetGroup = var.target_group_name + accountId = var.account_id + } + + metrics = [ + { label = "Avg", expression = "avg(rate(nginx_ingress_controller_request_duration_seconds_sum{host=\"${var.host}\"}[${var.period}m])) / avg(rate(nginx_ingress_controller_request_duration_seconds_count{host=\"${var.host}\"}[${var.period}m]))" }, + { label = "Max", expression = "max(rate(nginx_ingress_controller_request_duration_seconds_sum{host=\"${var.host}\"}[${var.period}m])) / max(rate(nginx_ingress_controller_request_duration_seconds_count{host=\"${var.host}\"}[${var.period}m]))" }, + { label = "Acceptable", expression = "${var.acceptable}" }, + { label = "Problem", expression = "${var.problem}" }, + ] +} diff --git a/modules/dashboard/modules/widgets/container/response-time/output.tf b/modules/dashboard/modules/widgets/container/response-time/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/container/response-time/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/container/response-time/variables.tf b/modules/dashboard/modules/widgets/container/response-time/variables.tf new file mode 100644 index 0000000..eedc7aa --- /dev/null +++ b/modules/dashboard/modules/widgets/container/response-time/variables.tf @@ -0,0 +1,72 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "host" { + type = string + default = null +} + +variable "problem" { + type = number + default = 2 + description = "The number which indicates the max timeout above which we have problem" +} + +variable "acceptable" { + type = number + default = 1 + description = "The number which indicates the acceptable timeout" +} + +variable "target_group_name" { + type = string + default = null +} + +variable "balancer_name" { + type = string + default = null +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/container/restarts/README.md b/modules/dashboard/modules/widgets/container/restarts/README.md new file mode 100644 index 0000000..e49257a --- /dev/null +++ b/modules/dashboard/modules/widgets/container/restarts/README.md @@ -0,0 +1,42 @@ +# restarts + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [cluster](#input\_cluster) | n/a | `string` | n/a | yes | +| [container](#input\_container) | n/a | `string` | n/a | yes | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [namespace](#input\_namespace) | n/a | `string` | `"default"` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/container/restarts/base.tf b/modules/dashboard/modules/widgets/container/restarts/base.tf new file mode 100644 index 0000000..4f96778 --- /dev/null +++ b/modules/dashboard/modules/widgets/container/restarts/base.tf @@ -0,0 +1,24 @@ +module "base" { + source = "../../base" + + name = "Restarts [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + stat = "Maximum" + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + ClusterName = var.cluster + Namespace = var.namespace + PodName = var.container + accountId = var.account_id + } + + metrics = [ + { label = "Restarts", expression = "sum(rate(kube_pod_container_status_restarts_total{container=\"${var.container}\", namespace=\"${var.namespace}\"}[${var.period}m])) by (container)" }, + ] +} diff --git a/modules/dashboard/modules/widgets/container/restarts/output.tf b/modules/dashboard/modules/widgets/container/restarts/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/container/restarts/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/container/restarts/variables.tf b/modules/dashboard/modules/widgets/container/restarts/variables.tf new file mode 100644 index 0000000..c8067bd --- /dev/null +++ b/modules/dashboard/modules/widgets/container/restarts/variables.tf @@ -0,0 +1,58 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "container" { + type = string +} + +variable "cluster" { + type = string +} + +variable "namespace" { + type = string + default = "default" +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/custom/README.md b/modules/dashboard/modules/widgets/custom/README.md new file mode 100644 index 0000000..78cdc5c --- /dev/null +++ b/modules/dashboard/modules/widgets/custom/README.md @@ -0,0 +1,47 @@ +# custom + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Enables anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [decimals](#input\_decimals) | The decimals to enable on numbers | `number` | `0` | no | +| [expressions](#input\_expressions) | Custom metric expressions over metrics, note that metrics have auto generated m1,m2,..., m{n} ids |
list(object({
expression = string
label = optional(string, null)
accountId = optional(string, null)
visible = optional(bool, null)
color = optional(string, null)
yAxis = optional(string, null)
region = optional(string, null)
}))
| `[]` | no | +| [fillOpacity](#input\_fillOpacity) | The fillOpacity value | `number` | `0` | no | +| [metrics](#input\_metrics) | n/a | `any` | n/a | yes | +| [period](#input\_period) | n/a | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | +| [stat](#input\_stat) | n/a | `string` | `"Average"` | no | +| [title](#input\_title) | n/a | `string` | n/a | yes | +| [view](#input\_view) | The view for log insights and alarm widgets | `string` | `null` | no | +| [yAxis](#input\_yAxis) | Widget Item common yAxis option (applied only metric type widgets). | `any` |
{
"left": {
"min": 0
}
}
| no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/custom/base.tf b/modules/dashboard/modules/widgets/custom/base.tf new file mode 100644 index 0000000..839c90b --- /dev/null +++ b/modules/dashboard/modules/widgets/custom/base.tf @@ -0,0 +1,25 @@ +module "base" { + source = "../base" + + name = var.title + data_source = var.data_source + coordinates = var.coordinates + period = var.period + stat = var.stat + yAxis = var.yAxis + view = var.view + decimals = var.decimals + fillOpacity = var.fillOpacity + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + accountId = var.account_id + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + } + + metrics = var.metrics + expressions = var.expressions +} diff --git a/modules/dashboard/modules/widgets/custom/output.tf b/modules/dashboard/modules/widgets/custom/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/custom/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/custom/variables.tf b/modules/dashboard/modules/widgets/custom/variables.tf new file mode 100644 index 0000000..0f84ebf --- /dev/null +++ b/modules/dashboard/modules/widgets/custom/variables.tf @@ -0,0 +1,101 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "title" { + type = string +} + +variable "metrics" { + type = any + ## sample form + # [ + # { MetricName = "errors1", "ClusterName" = "my-cluster-name" }, + # { MetricName = "errors2", "color" = "#d62728" } + # { MetricName = "errors3", accountId : "123234365765", "color" = "#d62728" } + # ] +} + +variable "expressions" { + type = list(object({ + expression = string + label = optional(string, null) + accountId = optional(string, null) + visible = optional(bool, null) + color = optional(string, null) + yAxis = optional(string, null) + region = optional(string, null) + })) + default = [] + description = "Custom metric expressions over metrics, note that metrics have auto generated m1,m2,..., m{n} ids" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +variable "period" { + type = number + default = 3 +} + +variable "stat" { + type = string + default = "Average" +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Enables anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} + +variable "yAxis" { + type = any + default = { left = { min = 0 } } + description = "Widget Item common yAxis option (applied only metric type widgets)." +} + +variable "view" { + type = string + default = null + description = "The view for log insights and alarm widgets" +} + +variable "decimals" { + type = number + default = 0 + description = "The decimals to enable on numbers" +} + +variable "fillOpacity" { + type = number + default = 0 + description = "The fillOpacity value" +} diff --git a/modules/dashboard/modules/widgets/ingress/connections/README.md b/modules/dashboard/modules/widgets/ingress/connections/README.md new file mode 100644 index 0000000..1d80844 --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/connections/README.md @@ -0,0 +1,40 @@ +# connections + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [ingress\_type](#input\_ingress\_type) | n/a | `string` | `"nginx"` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/ingress/connections/base.tf b/modules/dashboard/modules/widgets/ingress/connections/base.tf new file mode 100644 index 0000000..a30c106 --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/connections/base.tf @@ -0,0 +1,21 @@ +module "base" { + source = "../../base" + + name = "Connections (${var.ingress_type}) [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + accountId = var.account_id + } + + metrics = [ + { label : "1m range", expression : "sum(rate(nginx_ingress_controller_nginx_process_connections[1m]))" }, + { label : "${var.period}m range", expression : "sum(rate(nginx_ingress_controller_nginx_process_connections[${var.period}m]))" }, + ] +} diff --git a/modules/dashboard/modules/widgets/ingress/connections/output.tf b/modules/dashboard/modules/widgets/ingress/connections/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/connections/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/ingress/connections/variables.tf b/modules/dashboard/modules/widgets/ingress/connections/variables.tf new file mode 100644 index 0000000..f0d2ddc --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/connections/variables.tf @@ -0,0 +1,50 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "ingress_type" { + type = string + default = "nginx" +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/ingress/request-count/README.md b/modules/dashboard/modules/widgets/ingress/request-count/README.md new file mode 100644 index 0000000..2dbe1a0 --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/request-count/README.md @@ -0,0 +1,42 @@ +# request-count + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [by\_host](#input\_by\_host) | n/a | `bool` | `false` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [ingress\_type](#input\_ingress\_type) | n/a | `string` | `"nginx"` | no | +| [only\_5xx](#input\_only\_5xx) | n/a | `bool` | `false` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/ingress/request-count/base.tf b/modules/dashboard/modules/widgets/ingress/request-count/base.tf new file mode 100644 index 0000000..c13db97 --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/request-count/base.tf @@ -0,0 +1,29 @@ +module "base" { + source = "../../base" + + name = "Request count ${var.only_5xx ? "5XX" : "All"} (${var.ingress_type})${var.by_host ? " by host" : ""} [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + accountId = var.account_id + } + + metrics = concat(var.only_5xx ? [] : [ + { label = var.by_host ? "{{host}}/Total" : "Total", expression = "sum(rate(nginx_ingress_controller_requests[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + { label = var.by_host ? "{{host}}/2xx" : "2xx", expression = "sum(rate(nginx_ingress_controller_requests{status=~\"2..\"}[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + { label = var.by_host ? "{{host}}/3xx" : "3xx", expression = "sum(rate(nginx_ingress_controller_requests{status=~\"3..\"}[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + { label = var.by_host ? "{{host}}/4xx" : "4xx", expression = "sum(rate(nginx_ingress_controller_requests{status=~\"4..\"}[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + ], [ + { label = var.by_host ? "{{host}}/499" : "499", expression = "sum(rate(nginx_ingress_controller_requests{status=\"499\"}[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + { label = var.by_host ? "{{host}}/5XX" : "5XX", expression = "sum(rate(nginx_ingress_controller_requests{status=~\"5..\"}[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + { label = var.by_host ? "{{host}}/500" : "500", expression = "sum(rate(nginx_ingress_controller_requests{status=\"500\"}[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + { label = var.by_host ? "{{host}}/502" : "502", expression = "sum(rate(nginx_ingress_controller_requests{status=\"502\"}[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + { label = var.by_host ? "{{host}}/503" : "503", expression = "sum(rate(nginx_ingress_controller_requests{status=\"503\"}[${var.period}m]))${var.by_host ? " by (host)" : ""}" }, + ]) +} diff --git a/modules/dashboard/modules/widgets/ingress/request-count/output.tf b/modules/dashboard/modules/widgets/ingress/request-count/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/request-count/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/ingress/request-count/variables.tf b/modules/dashboard/modules/widgets/ingress/request-count/variables.tf new file mode 100644 index 0000000..54859da --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/request-count/variables.tf @@ -0,0 +1,60 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "ingress_type" { + type = string + default = "nginx" +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "only_5xx" { + type = bool + default = false +} + +variable "by_host" { + type = bool + default = false +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/ingress/request-rate/README.md b/modules/dashboard/modules/widgets/ingress/request-rate/README.md new file mode 100644 index 0000000..0d94658 --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/request-rate/README.md @@ -0,0 +1,41 @@ +# request-rate + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [by\_host](#input\_by\_host) | n/a | `bool` | `false` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [ingress\_type](#input\_ingress\_type) | n/a | `string` | `"nginx"` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/ingress/request-rate/base.tf b/modules/dashboard/modules/widgets/ingress/request-rate/base.tf new file mode 100644 index 0000000..fcd0bb7 --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/request-rate/base.tf @@ -0,0 +1,23 @@ +module "base" { + source = "../../base" + + name = "Requests rate (${var.ingress_type})${var.by_host ? " by host" : ""} [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + accountId = var.account_id + } + + metrics = var.by_host ? [ + { label : "__auto", expression : "sum(rate(nginx_ingress_controller_requests[${var.period}m])) by (host)" }, + ] : [ + { label : "1m range", expression : "sum(rate(nginx_ingress_controller_requests[1m]))" }, + { label : "${var.period}m range", expression : "sum(rate(nginx_ingress_controller_requests[${var.period}m]))" } + ] +} diff --git a/modules/dashboard/modules/widgets/ingress/request-rate/output.tf b/modules/dashboard/modules/widgets/ingress/request-rate/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/request-rate/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/ingress/request-rate/variables.tf b/modules/dashboard/modules/widgets/ingress/request-rate/variables.tf new file mode 100644 index 0000000..422e3d2 --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/request-rate/variables.tf @@ -0,0 +1,55 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "ingress_type" { + type = string + default = "nginx" +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "by_host" { + type = bool + default = false +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/ingress/response-time/README.md b/modules/dashboard/modules/widgets/ingress/response-time/README.md new file mode 100644 index 0000000..c2a6cd6 --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/response-time/README.md @@ -0,0 +1,43 @@ +# response-time + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [acceptable](#input\_acceptable) | The number which indicates the acceptable timeout | `number` | `1` | no | +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [anomaly\_detection](#input\_anomaly\_detection) | Allow to enable anomaly detection on widget metrics | `bool` | `false` | no | +| [anomaly\_deviation](#input\_anomaly\_deviation) | Deviation of the anomaly band | `number` | `6` | no | +| [by\_host](#input\_by\_host) | n/a | `bool` | `false` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [ingress\_type](#input\_ingress\_type) | n/a | `string` | `"nginx"` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [problem](#input\_problem) | The number which indicates the max timeout above which we have problem | `number` | `2` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/ingress/response-time/base.tf b/modules/dashboard/modules/widgets/ingress/response-time/base.tf new file mode 100644 index 0000000..13be2ce --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/response-time/base.tf @@ -0,0 +1,26 @@ +module "base" { + source = "../../base" + + name = "Response time (${var.ingress_type})${var.by_host ? " by host" : ""} [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + period = var.period + region = var.region + anomaly_detection = var.anomaly_detection + anomaly_deviation = var.anomaly_deviation + + defaults = { + MetricNamespace = "ContainerInsights" + accountId = var.account_id + } + + metrics = var.by_host ? [ + { label : "__auto", expression : "avg(increase(nginx_ingress_controller_request_duration_seconds_sum[${var.period}m])) by (host) * 100" }, + { label : "__auto", expression : "max(increase(nginx_ingress_controller_request_duration_seconds_sum[${var.period}m])) by (host)" }, + ] : [ + { label = "Avg", expression = "avg(rate(nginx_ingress_controller_request_duration_seconds_sum[${var.period}m])) / avg(rate(nginx_ingress_controller_request_duration_seconds_count[${var.period}m]))" }, + { label = "Max", expression = "max(rate(nginx_ingress_controller_request_duration_seconds_sum[${var.period}m])) / max(rate(nginx_ingress_controller_request_duration_seconds_count[${var.period}m]))" }, + { label = "Acceptable", expression = "${var.acceptable}" }, + { label = "Problem", expression = "${var.problem}" }, + ] +} diff --git a/modules/dashboard/modules/widgets/ingress/response-time/output.tf b/modules/dashboard/modules/widgets/ingress/response-time/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/response-time/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/ingress/response-time/variables.tf b/modules/dashboard/modules/widgets/ingress/response-time/variables.tf new file mode 100644 index 0000000..f17d3be --- /dev/null +++ b/modules/dashboard/modules/widgets/ingress/response-time/variables.tf @@ -0,0 +1,67 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "ingress_type" { + type = string + default = "nginx" +} + +variable "problem" { + type = number + default = 2 + description = "The number which indicates the max timeout above which we have problem" +} + +variable "acceptable" { + type = number + default = 1 + description = "The number which indicates the acceptable timeout" +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} + +variable "by_host" { + type = bool + default = false +} + +variable "anomaly_detection" { + type = bool + default = false + description = "Allow to enable anomaly detection on widget metrics" +} + +variable "anomaly_deviation" { + type = number + default = 6 + description = "Deviation of the anomaly band" +} diff --git a/modules/dashboard/modules/widgets/sla-slo-sli/latency/README.md b/modules/dashboard/modules/widgets/sla-slo-sli/latency/README.md new file mode 100644 index 0000000..078260f --- /dev/null +++ b/modules/dashboard/modules/widgets/sla-slo-sli/latency/README.md @@ -0,0 +1,39 @@ +# latency + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [balancer\_name](#input\_balancer\_name) | n/a | `string` | `null` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [histogram](#input\_histogram) | n/a | `bool` | `false` | no | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/sla-slo-sli/latency/base.tf b/modules/dashboard/modules/widgets/sla-slo-sli/latency/base.tf new file mode 100644 index 0000000..48832a3 --- /dev/null +++ b/modules/dashboard/modules/widgets/sla-slo-sli/latency/base.tf @@ -0,0 +1,25 @@ +module "base" { + source = "../../base" + + name = "${var.histogram ? "Request Duration histogram" : "Latency"} [${var.period}m]" + data_source = var.data_source + coordinates = var.coordinates + decimals = var.histogram ? null : 3 + period = var.period + region = var.region + view = var.histogram ? "histogram" : "gauge" + fillOpacity = 80 + + defaults = { + MetricNamespace = "AWS/ApplicationELB" + LoadBalancer = var.balancer_name + accountId = var.account_id + } + + metrics = var.histogram ? [ + { label : "Duration", expression : "histogram_quantile(0.90, sum(rate(nginx_ingress_controller_request_duration_seconds_bucket[${var.period}m])) by (le))" }, + ] : [ + { label : "AVG", expression : "avg(increase(nginx_ingress_controller_request_duration_seconds_sum[${var.period}m])) / 10" }, + { label : "MAX", expression : "max(increase(nginx_ingress_controller_request_duration_seconds_sum[${var.period}m])) / 10" } + ] +} diff --git a/modules/dashboard/modules/widgets/sla-slo-sli/latency/output.tf b/modules/dashboard/modules/widgets/sla-slo-sli/latency/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/sla-slo-sli/latency/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/sla-slo-sli/latency/variables.tf b/modules/dashboard/modules/widgets/sla-slo-sli/latency/variables.tf new file mode 100644 index 0000000..0562582 --- /dev/null +++ b/modules/dashboard/modules/widgets/sla-slo-sli/latency/variables.tf @@ -0,0 +1,43 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "balancer_name" { + type = string + default = null +} + +variable "histogram" { + type = bool + default = false +} + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} diff --git a/modules/dashboard/modules/widgets/sla-slo-sli/main/README.md b/modules/dashboard/modules/widgets/sla-slo-sli/main/README.md new file mode 100644 index 0000000..9fa1a0b --- /dev/null +++ b/modules/dashboard/modules/widgets/sla-slo-sli/main/README.md @@ -0,0 +1,38 @@ +# main + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [base](#module\_base) | ../../base | n/a | + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [account\_id](#input\_account\_id) | n/a | `string` | `null` | no | +| [balancer\_name](#input\_balancer\_name) | n/a | `string` | `null` | no | +| [coordinates](#input\_coordinates) | position |
object({
x : number
y : number
width : number
height : number
})
| n/a | yes | +| [data\_source](#input\_data\_source) | The custom datasource for widget item |
object({
uid = optional(string, null)
type = optional(string, "prometheus")
})
| n/a | yes | +| [period](#input\_period) | stats | `number` | `3` | no | +| [region](#input\_region) | n/a | `string` | `""` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/sla-slo-sli/main/base.tf b/modules/dashboard/modules/widgets/sla-slo-sli/main/base.tf new file mode 100644 index 0000000..3cf3932 --- /dev/null +++ b/modules/dashboard/modules/widgets/sla-slo-sli/main/base.tf @@ -0,0 +1,54 @@ +module "base" { + source = "../../base" + + name = "Availability (1d) to be extended" + data_source = var.data_source + coordinates = var.coordinates + decimals = 3 + stat = "Sum" + period = var.period + region = var.region + view = "gauge" + yAxis = { left = { min = 85, max = 100 } } + + setPeriodToTimeRange = true + singleValueFullPrecision = false + sparkline = false + stacked = false + start = "-PT8640H" + trend = false + end = "P0D" + annotations = { + horizontal = [ + { + color : "#3ECE76", + label : "Great", + value : 99.9, + fill : "below" + }, + { + color : "#FFC300", + label : "Good", + value : 99, + fill : "below" + }, + { + color : "#FF0F3C", + label : "Bad", + value : 90, + fill : "below" + } + ] + } + + + defaults = { + MetricNamespace = "AWS/ApplicationELB" + LoadBalancer = var.balancer_name + accountId = var.account_id + } + + metrics = [ + { label = "__auto", expression = "(1 - (sum(rate(nginx_ingress_controller_requests{status=~\"5[0-9][0-9]\"}[1d])) / sum(rate(nginx_ingress_controller_requests[1d])))) * 100" } + ] +} diff --git a/modules/dashboard/modules/widgets/sla-slo-sli/main/output.tf b/modules/dashboard/modules/widgets/sla-slo-sli/main/output.tf new file mode 100644 index 0000000..37309dc --- /dev/null +++ b/modules/dashboard/modules/widgets/sla-slo-sli/main/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = module.base.data +} diff --git a/modules/dashboard/modules/widgets/sla-slo-sli/main/variables.tf b/modules/dashboard/modules/widgets/sla-slo-sli/main/variables.tf new file mode 100644 index 0000000..01fec5e --- /dev/null +++ b/modules/dashboard/modules/widgets/sla-slo-sli/main/variables.tf @@ -0,0 +1,39 @@ +variable "data_source" { + type = object({ + uid = optional(string, null) + type = optional(string, "prometheus") + }) + description = "The custom datasource for widget item" +} + +variable "balancer_name" { + type = string + default = null +} + + +variable "account_id" { + type = string + default = null +} + +variable "region" { + type = string + default = "" +} + +# position +variable "coordinates" { + type = object({ + x : number + y : number + width : number + height : number + }) +} + +# stats +variable "period" { + type = number + default = 3 +} diff --git a/modules/dashboard/modules/widgets/text/title-with-collapse/README.md b/modules/dashboard/modules/widgets/text/title-with-collapse/README.md new file mode 100644 index 0000000..ceab05d --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title-with-collapse/README.md @@ -0,0 +1,32 @@ +# title-with-collapse + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [text](#input\_text) | n/a | `string` | n/a | yes | +| [y](#input\_y) | n/a | `number` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/text/title-with-collapse/local.tf b/modules/dashboard/modules/widgets/text/title-with-collapse/local.tf new file mode 100644 index 0000000..43f6f18 --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title-with-collapse/local.tf @@ -0,0 +1,14 @@ +locals { + data = { + type = "row" + collapsed = false + panels = [] + title = var.text + gridPos = { + x = 0 + y = var.y + h = 2 + w = 24 + } + } +} diff --git a/modules/dashboard/modules/widgets/text/title-with-collapse/output.tf b/modules/dashboard/modules/widgets/text/title-with-collapse/output.tf new file mode 100644 index 0000000..b140c6e --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title-with-collapse/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = local.data +} diff --git a/modules/dashboard/modules/widgets/text/title-with-collapse/variables.tf b/modules/dashboard/modules/widgets/text/title-with-collapse/variables.tf new file mode 100644 index 0000000..afbc645 --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title-with-collapse/variables.tf @@ -0,0 +1,7 @@ +variable "text" { + type = string +} + +variable "y" { + type = number +} diff --git a/modules/dashboard/modules/widgets/text/title-with-link/README.md b/modules/dashboard/modules/widgets/text/title-with-link/README.md new file mode 100644 index 0000000..d27ed66 --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title-with-link/README.md @@ -0,0 +1,34 @@ +# title-with-link + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [link\_to\_jump](#input\_link\_to\_jump) | The URL to wich the user can be redirected after clicking on the title | `string` | n/a | yes | +| [text](#input\_text) | n/a | `string` | n/a | yes | +| [width](#input\_width) | n/a | `number` | `24` | no | +| [y](#input\_y) | n/a | `number` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/text/title-with-link/local.tf b/modules/dashboard/modules/widgets/text/title-with-link/local.tf new file mode 100644 index 0000000..36c6ed6 --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title-with-link/local.tf @@ -0,0 +1,22 @@ +locals { + data = { + type = "text", + datasource = { + type = "datasource" + uid = "grafana" + } + options = { + code = { + language = "plaintext" + } + content = "## [${var.text}](${var.link_to_jump})" + mode = "markdown" + } + gridPos = { + x = 0 + y = var.y + h = 2 + w = var.width + } + } +} diff --git a/modules/dashboard/modules/widgets/text/title-with-link/output.tf b/modules/dashboard/modules/widgets/text/title-with-link/output.tf new file mode 100644 index 0000000..b140c6e --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title-with-link/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = local.data +} diff --git a/modules/dashboard/modules/widgets/text/title-with-link/variables.tf b/modules/dashboard/modules/widgets/text/title-with-link/variables.tf new file mode 100644 index 0000000..9f35840 --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title-with-link/variables.tf @@ -0,0 +1,17 @@ +variable "text" { + type = string +} + +variable "link_to_jump" { + type = string + description = "The URL to wich the user can be redirected after clicking on the title" +} + +variable "y" { + type = number +} + +variable "width" { + type = number + default = 24 +} diff --git a/modules/dashboard/modules/widgets/text/title/README.md b/modules/dashboard/modules/widgets/text/title/README.md new file mode 100644 index 0000000..a3aa6cc --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title/README.md @@ -0,0 +1,33 @@ +# title + + +## Requirements + +No requirements. + +## Providers + +No providers. + +## Modules + +No modules. + +## Resources + +No resources. + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [collapse](#input\_collapse) | Whether the title block will be collapse to wrap next coming blocks | `bool` | `true` | no | +| [text](#input\_text) | n/a | `string` | n/a | yes | +| [y](#input\_y) | n/a | `number` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [data](#output\_data) | n/a | + diff --git a/modules/dashboard/modules/widgets/text/title/local.tf b/modules/dashboard/modules/widgets/text/title/local.tf new file mode 100644 index 0000000..e6596fd --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title/local.tf @@ -0,0 +1,22 @@ +locals { + data = { + type = "text" + datasource = { + type = "datasource" + uid = "grafana" + } + options = { + code = { + language = "plaintext" + } + content = "## ${var.text}" + mode = "markdown" + } + gridPos = { + x = 0 + y = var.y + h = 2 + w = 24 + } + } +} diff --git a/modules/dashboard/modules/widgets/text/title/output.tf b/modules/dashboard/modules/widgets/text/title/output.tf new file mode 100644 index 0000000..b140c6e --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title/output.tf @@ -0,0 +1,3 @@ +output "data" { + value = local.data +} diff --git a/modules/dashboard/modules/widgets/text/title/variables.tf b/modules/dashboard/modules/widgets/text/title/variables.tf new file mode 100644 index 0000000..512333f --- /dev/null +++ b/modules/dashboard/modules/widgets/text/title/variables.tf @@ -0,0 +1,13 @@ +variable "text" { + type = string +} + +variable "y" { + type = number +} + +variable "collapse" { + type = bool + default = true + description = "Whether the title block will be collapse to wrap next coming blocks" +} diff --git a/modules/dashboard/output.tf b/modules/dashboard/output.tf new file mode 100644 index 0000000..4855be9 --- /dev/null +++ b/modules/dashboard/output.tf @@ -0,0 +1,6 @@ +output "dump" { + value = { + widget_config = local.widget_config + widget_result = local.widget_result + } +} diff --git a/modules/dashboard/variables.tf b/modules/dashboard/variables.tf new file mode 100644 index 0000000..dfe9d55 --- /dev/null +++ b/modules/dashboard/variables.tf @@ -0,0 +1,53 @@ +variable "name" { + type = string + description = "Dashboard name. Should not contain spaces and special chars." +} + +variable "defaults" { + type = any + ## valid values + # object({ + # period = number + # namespace = string + # cluster = string + # width = number + # height = number + # } + # ) + default = {} + description = "Default values to be supplied to all modules." +} + +variable "rows" { + type = any + description = "List of widgets to be inserted into the dashboard. See ./modules/widgets folder to see list of available widgets." +} + +variable "data_source" { + type = object({ + uid = string + type = optional(string, "prometheus") + }) + description = "The grafana dashboard global/default datasource, will be used in widget items if they have no their custom ones" +} + +variable "variables" { + type = list(object({ + name = string + type = optional(string, "custom") + hide = optional(number, 0) + includeAll = optional(bool, false) + multi = optional(bool, false) + query = optional(string, "") + queryValue = optional(string, "") + skipUrlSync = optional(bool, false) + options = optional(list(object({ + selected = optional(bool, false) + value = string + text = optional(string, null) + })), []) + } + )) + default = [] + description = "Allows to define variables to be used in dashboard" +} diff --git a/modules/dashboard/versions.tf b/modules/dashboard/versions.tf new file mode 100644 index 0000000..bdf8b9d --- /dev/null +++ b/modules/dashboard/versions.tf @@ -0,0 +1,16 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + + grafana = { + source = "grafana/grafana" + version = ">= 3.7.0" + } + + random = { + source = "hashicorp/random" + version = ">= 3.6.2" + } + } +} diff --git a/modules/dashboard/widgets-container.tf b/modules/dashboard/widgets-container.tf new file mode 100644 index 0000000..d83c7cc --- /dev/null +++ b/modules/dashboard/widgets-container.tf @@ -0,0 +1,144 @@ +# Container widgets +module "container_cpu_widget" { + source = "./modules/widgets/container/cpu" + + for_each = { for index, item in try(local.widget_config["container/cpu"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + + # container + container = each.value.container + cluster = try(each.value.cluster, null) + namespace = each.value.namespace + by_pod = try(each.value.by_pod, false) + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "container_memory_widget" { + source = "./modules/widgets/container/memory" + + for_each = { for index, item in try(local.widget_config["container/memory"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + + # container + container = each.value.container + cluster = each.value.cluster + namespace = each.value.namespace + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "container_network_widget" { + source = "./modules/widgets/container/network" + + for_each = { for index, item in try(local.widget_config["container/network"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + + # container + container = each.value.container + cluster = each.value.cluster + namespace = each.value.namespace + host = try(each.value.host, null) + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "container_replicas_widget" { + source = "./modules/widgets/container/replicas" + + for_each = { for index, item in try(local.widget_config["container/replicas"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + + # container + container = each.value.container + cluster = each.value.cluster + namespace = each.value.namespace + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "container_restarts_widget" { + source = "./modules/widgets/container/restarts" + + for_each = { for index, item in try(local.widget_config["container/restarts"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + + # container + container = each.value.container + cluster = each.value.cluster + namespace = each.value.namespace + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "container_request_count_widget" { + source = "./modules/widgets/container/request-count" + + for_each = { for index, item in try(local.widget_config["container/request-count"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + + # container + host = each.value.host + target_group_name = try(each.value.target_group_name, null) + only_5xx = try(each.value.only_5xx, false) + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "container_response_time_widget" { + source = "./modules/widgets/container/response-time" + + for_each = { for index, item in try(local.widget_config["container/response-time"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + acceptable = try(each.value.acceptable, 1) + problem = try(each.value.problem, 2) + + # container + host = each.value.host + balancer_name = try(each.value.balancer_name, null) + target_group_name = try(each.value.target_group_name, null) + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} diff --git a/modules/dashboard/widgets-ingress.tf b/modules/dashboard/widgets-ingress.tf new file mode 100644 index 0000000..1b50970 --- /dev/null +++ b/modules/dashboard/widgets-ingress.tf @@ -0,0 +1,67 @@ +# Ingress widgets + +module "ingress_connections_widget" { + source = "./modules/widgets/ingress/connections" + + for_each = { for index, item in try(local.widget_config["ingress/connections"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "ingress_request_rate_widget" { + source = "./modules/widgets/ingress/request-rate" + + for_each = { for index, item in try(local.widget_config["ingress/request-rate"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + by_host = try(each.value.by_host, false) + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "ingress_request_count_widget" { + source = "./modules/widgets/ingress/request-count" + + for_each = { for index, item in try(local.widget_config["ingress/request-count"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + by_host = try(each.value.by_host, false) + only_5xx = try(each.value.only_5xx, false) + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} + +module "ingress_response_time_widget" { + source = "./modules/widgets/ingress/response-time" + + for_each = { for index, item in try(local.widget_config["ingress/response-time"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + by_host = try(each.value.by_host, false) + acceptable = try(each.value.acceptable, 1) + problem = try(each.value.problem, 2) + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} diff --git a/modules/dashboard/widgets-single.tf b/modules/dashboard/widgets-single.tf new file mode 100644 index 0000000..d76bc41 --- /dev/null +++ b/modules/dashboard/widgets-single.tf @@ -0,0 +1,24 @@ +# Single widgets +module "widget_custom" { + source = "./modules/widgets/custom" + + for_each = { for index, item in try(local.widget_config["custom"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + view = try(each.value.view, null) + decimals = try(each.value.decimals, 0) + fillOpacity = try(each.value.fillOpacity, 0) + yAxis = each.value.yAxis + + # metric + title = each.value.title + metrics = each.value.metrics + expressions = each.value.expressions + + account_id = each.value.account_id + region = each.value.region + anomaly_detection = each.value.anomaly_detection + anomaly_deviation = each.value.anomaly_deviation +} diff --git a/modules/dashboard/widgets-sla-slo-sli.tf b/modules/dashboard/widgets-sla-slo-sli.tf new file mode 100644 index 0000000..4e45c3e --- /dev/null +++ b/modules/dashboard/widgets-sla-slo-sli.tf @@ -0,0 +1,30 @@ +# SLA/SLO/SLI widgets + +module "widget_sla_slo_sli_main" { + source = "./modules/widgets/sla-slo-sli/main" + + for_each = { for index, item in try(local.widget_config["sla-slo-sli/main"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + + balancer_name = try(each.value.balancer_name, null) + + account_id = each.value.account_id +} + +module "widget_sla_slo_sli_latency" { + source = "./modules/widgets/sla-slo-sli/latency" + + for_each = { for index, item in try(local.widget_config["sla-slo-sli/latency"], []) : index => item } + + data_source = each.value.data_source + coordinates = each.value.coordinates + period = each.value.period + histogram = try(each.value.histogram, false) + + balancer_name = try(each.value.balancer_name, null) + + account_id = each.value.account_id +} diff --git a/modules/dashboard/widgets-text.tf b/modules/dashboard/widgets-text.tf new file mode 100644 index 0000000..ae67319 --- /dev/null +++ b/modules/dashboard/widgets-text.tf @@ -0,0 +1,30 @@ +# Title +module "text_title" { + source = "./modules/widgets/text/title" + + for_each = { for index, item in try(local.widget_config["text/title"], []) : index => item } + + text = each.value.text + y = each.value.row +} + +# Title with Link +module "text_title_with_link" { + source = "./modules/widgets/text/title-with-link" + + for_each = { for index, item in try(local.widget_config["text/title-with-link"], []) : index => item } + + text = each.value.text + link_to_jump = each.value.link_to_jump + y = each.value.row +} + +# Title with collapse +module "text_title_with_collapse" { + source = "./modules/widgets/text/title-with-collapse" + + for_each = { for index, item in try(local.widget_config["text/title-with-collapse"], []) : index => item } + + text = each.value.text + y = each.value.row +} diff --git a/modules/dashboard/widgets_blocks.tf b/modules/dashboard/widgets_blocks.tf new file mode 100644 index 0000000..1a20c7b --- /dev/null +++ b/modules/dashboard/widgets_blocks.tf @@ -0,0 +1,34 @@ +# Widget blocks +module "block_ingress" { + source = "./modules/blocks/ingress" + + for_each = { for index, item in try(local.blocks_by_type["ingress"], []) : index => item } + + balancer_name = try(each.value.block.balancer_name, null) + account_id = try(each.value.block.account_id, null) + region = try(each.value.block.region, null) +} + +module "block_service" { + source = "./modules/blocks/service" + + for_each = { for index, item in try(local.blocks_by_type["service"], []) : index => item } + + name = each.value.block.name + balancer_name = try(each.value.block.balancer_name, null) + target_group_arn = try(each.value.block.target_group_arn, null) + healthcheck_id = try(each.value.block.healthcheck_id, null) + cluster = try(each.value.block.cluster, null) + namespace = try(each.value.block.namespace, "$namespace") + version_label = try(each.value.block.version_label, "app-version") + log_group_name = try(each.value.block.log_group_name, null) + host = try(each.value.block.host, null) +} + +module "block_sla" { + source = "./modules/blocks/sla" + + for_each = { for index, item in try(local.blocks_by_type["sla"], []) : index => item } + + balancer_name = try(each.value.block.balancer_name, null) +} diff --git a/modules/notifications/main.tf b/modules/notifications/main.tf index 57e15bf..ef2410b 100644 --- a/modules/notifications/main.tf +++ b/modules/notifications/main.tf @@ -1,4 +1,6 @@ resource "grafana_notification_policy" "policy" { + count = var.notifications.policy != null ? 1 : 0 + contact_point = var.notifications.contact_point group_by = var.notifications.group_by group_interval = var.notifications.group_interval diff --git a/output.tf b/output.tf new file mode 100644 index 0000000..1d59dac --- /dev/null +++ b/output.tf @@ -0,0 +1,5 @@ +output "data" { + value = { + application_dashboard = module.application_dashboard + } +} diff --git a/tests/base/0-setup.tf b/tests/base/0-setup.tf new file mode 100644 index 0000000..8bb1fbe --- /dev/null +++ b/tests/base/0-setup.tf @@ -0,0 +1,14 @@ +terraform { + required_providers { + grafana = { + source = "grafana/grafana" + version = ">= 3.7.0" + } + } +} + +# please start grafana locally using `docker compose up -d` (the compose.yaml is in ./tests folder) before running the test +provider "grafana" { + url = "http://localhost:3000" + auth = "admin:admin" +} diff --git a/tests/base/1-example.tf b/tests/base/1-example.tf new file mode 100644 index 0000000..94ecd01 --- /dev/null +++ b/tests/base/1-example.tf @@ -0,0 +1,35 @@ +module "this" { + source = "../.." + + name = "Test-dashboard" + + application_dashboard = { + rows : [ + { type : "block/sla" }, + { type : "block/ingress" }, + { type : "block/service", name : "service-name-1", host : "example.com" }, + { type : "block/service", name : "service-name-2" }, + { type : "block/service", name : "service-name-3" } + ] + data_source = { + uid : "00000" + } + variables = [ + { + "name" : "namespace", + "options" : [ + { + "selected" : true, + "value" : "prod" + }, + { + "value" : "stage" + }, + { + "value" : "dev" + } + ], + } + ] + } +} diff --git a/tests/base/README.md b/tests/base/README.md new file mode 100644 index 0000000..9ad1a89 --- /dev/null +++ b/tests/base/README.md @@ -0,0 +1,31 @@ +# base + + +## Requirements + +| Name | Version | +|------|---------| +| [grafana](#requirement\_grafana) | >= 3.7.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | ../.. | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/tests/compose.yaml b/tests/compose.yaml new file mode 100644 index 0000000..d03eae3 --- /dev/null +++ b/tests/compose.yaml @@ -0,0 +1,5 @@ +services: + grafana: + image: grafana/grafana:11.1.1 + ports: + - 3000:3000 diff --git a/variables.tf b/variables.tf index 2c711d0..02adbbe 100644 --- a/variables.tf +++ b/variables.tf @@ -1,3 +1,41 @@ +# dashboard variables +variable "name" { + type = string + description = "Dashboard name" +} + +variable "application_dashboard" { + type = object({ + rows = optional(any, []) + data_source = object({ # global/default datasource, TODO: create datasource inside the module + uid = string + type = optional(string, "prometheus") + }) + variables = optional(list(object({ # Allows to define variables to be used in dashboard + name = string + type = optional(string, "custom") + hide = optional(number, 0) + includeAll = optional(bool, false) + multi = optional(bool, false) + query = optional(string, "") + queryValue = optional(string, "") + skipUrlSync = optional(bool, false) + options = optional(list(object({ + selected = optional(bool, false) + value = string + text = optional(string, null) + })), []) + })), []) + }) + default = { + rows = [], + data_source = null, + variables = [] + } + description = "Dashboard for monitoring applications" +} + +# alerting variables variable "alert_interval_seconds" { type = number default = 10 @@ -25,7 +63,7 @@ variable "alert_rules" { threshold = number # The value against which B blocks are compared in the math expression })) default = [] - description = "This varibale describes alert folders, groups and rules." + description = "This variable describes alert folders, groups and rules." } variable "slack_endpoints" { diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..7967425 --- /dev/null +++ b/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + grafana = { + source = "grafana/grafana" + version = ">= 3.7.0" + } + } +}