Terraform
Manage KubeSense dashboards, alert rules and integrations as code with the KubeSense Terraform provider, and move existing ones into Terraform with Export.
The KubeSense Terraform provider lets you keep your monitoring setup in code: reviewed in pull requests, versioned in git, and the same in every environment. Anything you already built in the UI can be exported to Terraform and brought under management without recreating it.
What you can manage
| Resource | Manages |
|---|---|
kubesense_dashboard | A dashboard, with panels, variables and layout as blocks |
kubesense_dashboard_json | A dashboard as one JSON string, if you prefer the raw preset |
kubesense_dashboard_list | A dashboard list (folder) and its members |
kubesense_alert_rule | An alert rule, with notification channels by name |
kubesense_integration | Jira, Confluence, Atlassian and custom MCP server integrations |
kubesense_aws_account | An AWS account integration |
kubesense_gcp_project | A GCP project integration |
kubesense_azure_subscription | An Azure subscription integration |
kubesense_mongodbatlas_org | A MongoDB Atlas organisation integration |
kubesense_confluent_org | A Confluent organisation integration |
kubesense_kong_control_plane | A Kong control plane integration |
Two data sources, kubesense_dashboard and kubesense_integration, read items Terraform does not manage, such as system dashboards and GitHub.
Step 1: Install Terraform
You need Terraform 1.5 or later. Exported code uses import blocks, which older versions do not support.
macOS (Homebrew):
brew tap hashicorp/tap
brew install hashicorp/tap/terraformLinux (Ubuntu or Debian):
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraformWindows (Chocolatey):
choco install terraformFor other systems, download the binary from developer.hashicorp.com/terraform/install and put it on your PATH.
Check that it works:
terraform -versionStep 2: Create a service account key
Terraform signs in with a service account key, not a personal login.
- Go to Settings → Service Accounts and create a service account. Give it a role with write access to what you will manage. Only administrators can do this.
- Open the account's ⋮ menu → Manage keys → Create key.
- Turn on the scopes you need at Editor: Dashboard for dashboards and lists, Alerts for alert rules, Integrations for integrations and cloud accounts.
- Click Generate key and copy it. It is shown only once.
The Use with Terraform section under the new key shows the provider block for your deployment.
Step 3: Configure the provider
Create a folder for your configuration with a main.tf:
terraform {
required_providers {
kubesense = {
source = "kubesense-ai/kubesense"
}
}
}
provider "kubesense" {
api_url = "https://<your-kubesense-host>"
# The API key comes from KUBESENSE_API_KEY.
}Keep the key out of the file. Set it in the shell, or in your CI as a secret:
export KUBESENSE_API_KEY=<your-key>api_url can also come from KUBESENSE_API_URL. Then install the provider:
terraform initStep 4: Export what you already have
Every dashboard, alert rule, integration and cloud account can be exported as ready-to-use Terraform.
| Item | Where |
|---|---|
| Dashboard | Open the dashboard → ⋮ → Export, or a row's ⋮ → Export in the dashboards list |
| Alert rule | Open the rule → Export (also in the footer of the rule's edit page) |
| Integration | Integrations → pick a provider → select an account → ⋮ → Export to Terraform |
| Cloud account | Integrations → pick a cloud → select an account → Export to Terraform |
For dashboards and alert rules, the Export window has two tabs, JSON and Terraform. Open Terraform, then Copy or Download (.tf), and add it to your folder.
note: On an alert rule's edit page, the Terraform tab shows the rule as saved. Save first to include your changes.
Secrets such as API keys and client secrets are never exported. They appear as Terraform variables for you to fill in, for example in a terraform.tfvars file or with TF_VAR_<name> in the environment.
Step 5: Apply to take it over
Exported code starts with an import block that carries the item's ID:
# Manages the existing one: the first apply adopts it instead of creating a copy.
# Delete this block to create a copy instead.
import {
to = kubesense_alert_rule.cpu_high
id = "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
resource "kubesense_alert_rule" "cpu_high" {
# ...
}Run a plan. It should only import:
terraform planPlan: 1 to import, 0 to add, 0 to change, 0 to destroy.Apply it, and the existing item is now managed by Terraform:
terraform applyA plan straight after reports No changes. You can keep the import block or delete it once applied. To create a copy instead (in another environment, say), delete the import block before the first apply.
warning: The name in quotes after the resource type ("cpu_high") is how Terraform tracks the item. Renaming it makes Terraform delete the item and create a new one. To rename the alert or dashboard itself, change its name attribute.
Step 6: Change it in code
Edit an attribute in the .tf file, for example an alert's threshold:
threshold_value = 95Then review and apply:
terraform plan # shows 1 to change
terraform applyThe item is updated in place, and the change shows up in KubeSense.
Step 7: Catch changes made in the UI
If someone edits a Terraform-managed item in the UI, the next terraform plan shows the difference. terraform apply puts it back to what the code says. Settle on one place to make changes so the two do not fight.
Writing resources by hand
You do not have to start from an export. Leave out what you do not need; KubeSense fills its defaults.
Alert rule
resource "kubesense_alert_rule" "cpu_high" {
name = "cpu high"
severity = "critical"
metric_query_label = "A"
threshold_operator = "greater_than"
threshold_value = 80
time_window = "5m"
evaluation_interval = "1m"
notification_channel_names = ["slack-oncall"]
query_config {
label = "A"
query_mode = "code"
selected_mode = "metrics"
promql = "avg(rate(container_cpu_usage_seconds_total[5m]))"
}
}A rule needs a notification channel, or route_by_labels = true, unless it is disabled (enabled = false).
Dashboard
resource "kubesense_dashboard" "checkout" {
name = "Checkout"
description = "Golden signals for checkout"
grid_layout {
i = "p1"
x = 0
y = 0
w = 12
h = 8
}
panel {
name = "CPU by pod"
panel_type = "timeSeries"
query {
label = "A"
query_mode = "code"
selected_mode = "metrics"
promql = "sum(rate(container_cpu_usage_seconds_total[5m])) by (pod)"
}
}
}Settings that have no block of their own are *_json attributes. Set them with jsonencode(), for example aggregation_json = jsonencode({ function = "row_count" }).
Integration
resource "kubesense_integration" "jira" {
integration_provider = "jira"
name = "jira-prod"
api_key = var.jira_api_token
config = {
deployment = "cloud"
base_url = "https://acme.atlassian.net"
username = "kubesense-bot@acme.com"
}
}
variable "jira_api_token" {
type = string
sensitive = true
}Cloud account
resource "kubesense_aws_account" "prod" {
account_id = "123456789012"
mode = "role_access"
role_arn = "arn:aws:iam::123456789012:role/KubeSenseCollectorRole"
resource_types = {
ec2 = { collect = true, metric_enabled = true }
rds = { collect = true, metric_enabled = true, metric_tier = "expanded" }
}
}Many alike items from one block
Use for_each to create several rules (or dashboards) from one definition:
locals {
cpu_limits = { api = 80, worker = 90 }
}
resource "kubesense_alert_rule" "cpu" {
for_each = local.cpu_limits
name = "cpu high: ${each.key}"
severity = "warning"
route_by_labels = true
labels = { component = each.key }
metric_query_label = "A"
threshold_operator = "greater_than"
threshold_value = each.value
time_window = "5m"
evaluation_interval = "1m"
query_config {
label = "A"
query_mode = "code"
selected_mode = "metrics"
promql = "avg(rate(container_cpu_usage_seconds_total{container=\"${each.key}\"}[5m]))"
}
}Good to know
-
Changes are checked before they are saved. Dashboards and alert rules from Terraform are held to the same rules as the editor, strictly. An invalid one is refused with a message saying what is wrong, and dashboard problems show up already at
terraform plan. -
Secrets are write-only. API keys and cloud secrets are never read back, so a change to them made outside Terraform is not detected, and they are not set on import.
-
Integrations test the connection. Creating an integration, or changing its credentials, makes KubeSense connect to it first. Apply from somewhere KubeSense can reach the target, with working credentials.
-
resource_typeson cloud accounts. When set, Terraform owns collection: types switched on in the UI but not listed are switched off on the next apply. When left out, collection is left alone. -
dashboard_idson dashboard lists. When set, Terraform owns membership: dashboards added in the UI are removed from the list on the next apply. When left out, membership is left alone. -
System dashboards are read-only. Their export is a data source you can read and build your own dashboard from:
data "kubesense_dashboard" "ec2" { name = "AWS EC2 Overview" } -
GitHub connects only through the UI. Read it with the
kubesense_integrationdata source.
Stop managing without deleting
terraform destroy, or removing a resource from the code, deletes the item in KubeSense. To stop managing an item but keep it, remove it from state instead:
terraform state rm kubesense_alert_rule.cpu_highTroubleshooting
| Message | What to do |
|---|---|
Missing api_key | Set KUBESENSE_API_KEY, or api_key in the provider block. |
HTTP 401 | The key is wrong, expired or revoked. Create a new one (Step 2). |
HTTP 403 | The key lacks the scope, or the role lacks write access, for that item. Add the scope at Editor. |
Alert rule does not match the alert schema: ... | The rule breaks a rule the editor also enforces; the message names the field. |
rule notifies nobody | Add notification_channel_names, set route_by_labels = true, or set enabled = false. |
Notification channel ... not found | The channel name does not exist in this deployment. Check its exact name under Alerts → Notification Channels. |
| A plan wants to delete and create the same item | The resource's name in quotes changed. Change it back, or use a moved block. |