Konnect Observability のダッシュボードを Terraform で管理する
はじめに
Kong Konnectには、Observabilityという機能が存在し、Konnectで管理しているAPIトラフィックに対するオブザーバビリティを提供します。機能の中には、ダッシュボードが存在し、これを活用することでAPIの開発者や運用者が必要な情報をまとめておくことが可能です。こちらの記事では、そのダッシュボードをTerraformで管理するための方法について解説します。
ダッシュボードのTerraform管理
まず、Konnect ObservabilityのダッシュボードをTerraformで管理しようと思うと、以下のようなリソース定義が必要です。
resource "konnect_dashboard" "example_dashboard" {
provider = konnect-beta
name = "Quick Summary Dashboard"
labels = {
"created_by" = "terraform"
}
definition = {
# ...
}
}
このとき、definition にはダッシュボードのJSON定義を格納するわけで、Konnectの画面を参照すると何やらJSONでエクスポートできそうなボタンが存在します。

JSONの定義をエクスポートするボタン
例えば、ダッシュボードテンプレートに存在する Quick Summary Dashboard をJSONでエクスポートすると、以下のような構成になっています。
{
"tiles": [
{
"type": "chart",
"layout": {
"size": {
"cols": 6,
"rows": 2
},
"position": {
"col": 0,
"row": 0
}
},
"definition": {
"chart": {
"type": "timeseries_line",
"chart_title": "Total traffic over time"
},
"query": {
"metrics": ["request_count"],
"datasource": "api_usage",
"dimensions": ["time"]
}
},
"id": "fae0f758-3f22-49ba-a79e-2aa80ae0456e"
},
{
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 0,
"row": 2
}
},
"definition": {
"chart": {
"type": "horizontal_bar",
"stacked": true,
"chart_title": "Top gateway services by requests"
},
"query": {
"filters": [
{
"field": "gateway_service",
"operator": "not_empty"
}
],
"metrics": ["request_count"],
"datasource": "api_usage",
"dimensions": ["gateway_service"]
}
},
"id": "1d5742c4-3793-4cf1-af34-25843d0f1225"
},
{
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 2,
"row": 2
}
},
"definition": {
"chart": {
"type": "horizontal_bar",
"stacked": true,
"chart_title": "Top routes by requests"
},
"query": {
"filters": [
{
"field": "route",
"operator": "not_empty"
}
],
"metrics": ["request_count"],
"datasource": "api_usage",
"dimensions": ["route"]
}
},
"id": "0ecad701-bb56-43fc-8484-68c609ca0404"
},
{
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 4,
"row": 2
}
},
"definition": {
"chart": {
"type": "horizontal_bar",
"stacked": true,
"chart_title": "Top consumers by requests"
},
"query": {
"filters": [
{
"field": "consumer",
"operator": "not_empty"
}
],
"metrics": ["request_count"],
"datasource": "api_usage",
"dimensions": ["consumer"]
}
},
"id": "7e26322d-ed49-45db-bea2-3bcdd8100420"
},
{
"type": "chart",
"layout": {
"size": {
"cols": 3,
"rows": 2
},
"position": {
"col": 0,
"row": 4
}
},
"definition": {
"chart": {
"type": "timeseries_line",
"chart_title": "Latency breakdown over time"
},
"query": {
"metrics": [
"response_latency_p99",
"response_latency_p95",
"response_latency_p50"
],
"datasource": "api_usage",
"dimensions": ["time"]
}
},
"id": "e40a73c3-1c52-44ca-92fc-f9047556cc71"
},
{
"type": "chart",
"layout": {
"size": {
"cols": 3,
"rows": 2
},
"position": {
"col": 3,
"row": 4
}
},
"definition": {
"chart": {
"type": "timeseries_line",
"chart_title": "Kong vs upstream latency over time"
},
"query": {
"metrics": ["upstream_latency_p99", "kong_latency_p99"],
"datasource": "api_usage",
"dimensions": ["time"]
}
},
"id": "b87e49de-f3bd-49d5-9f73-3e45e04de6b8"
},
{
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 0,
"row": 6
}
},
"definition": {
"chart": {
"type": "horizontal_bar",
"stacked": true,
"chart_title": "Slowest gateway services (average)"
},
"query": {
"filters": [
{
"field": "gateway_service",
"operator": "not_empty"
}
],
"metrics": ["response_latency_average"],
"datasource": "api_usage",
"dimensions": ["gateway_service"]
}
},
"id": "a24d4315-b85d-4e5c-ab9e-27944cfaa61d"
},
{
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 2,
"row": 6
}
},
"definition": {
"chart": {
"type": "horizontal_bar",
"stacked": true,
"chart_title": "Slowest routes (average)"
},
"query": {
"filters": [
{
"field": "route",
"operator": "not_empty"
}
],
"metrics": ["response_latency_average"],
"datasource": "api_usage",
"dimensions": ["route"]
}
},
"id": "6c5cd6a4-6d0b-4d22-93cf-2046087e7c67"
},
{
"type": "chart",
"layout": {
"size": {
"cols": 2,
"rows": 2
},
"position": {
"col": 4,
"row": 6
}
},
"definition": {
"chart": {
"type": "horizontal_bar",
"stacked": true,
"chart_title": "Slowest consumers (average)"
},
"query": {
"filters": [
{
"field": "consumer",
"operator": "not_empty"
}
],
"metrics": ["response_latency_average"],
"datasource": "api_usage",
"dimensions": ["consumer"]
}
},
"id": "2b0adbdf-a344-4137-a27d-cc382b8cb21c"
}
]
}
これをそのまま definition の中に入れればOK!というわけにはいきません。Terraform側のスキーマ(definition)を見てみましょう。
resource "konnect_dashboard" "my_dashboard" {
provider = konnect-beta
definition = {
preset_filters = [
{
field = "ai_provider"
operator = "not_in"
value = "{ \"see\": \"documentation\" }"
}
]
tiles = [
{
chart = {
definition = {
chart = {
choropleth_map = {
chart_title = "...my_chart_title..."
type = "choropleth_map"
}
donut = {
chart_title = "...my_chart_title..."
type = "donut"
}
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = [
"status_code_grouped",
]
filters = [
{
field = "realm"
operator = "not_empty"
value = "{ \"see\": \"documentation\" }"
}
]
granularity = "twelveHourly"
metrics = [
"kong_latency_p50"
]
time_range = {
relative = {
time_range = "current_week"
type = "relative"
tz = "...my_tz..."
}
}
}
llm_usage = {
datasource = "llm_usage"
dimensions = [
"consumer"
]
filters = [
{
field = "application"
operator = "empty"
value = "{ \"see\": \"documentation\" }"
}
]
granularity = "tenMinutely"
metrics = [
"ai_request_count"
]
time_range = {
absolute = {
end = "2022-11-26T07:30:44.592Z"
start = "2022-01-09T02:25:36.303Z"
type = "absolute"
tz = "...my_tz..."
}
}
}
}
}
layout = {
position = {
col = 4
row = 5
}
size = {
cols = 6
rows = 8
}
}
type = "chart"
}
}
]
}
labels = {
key = "value"
}
name = "...my_name..."
}
以下のような差分があることが確認できます。
- JSON: tiles -> [ { "layout": ..., "definition": ... } ]
- Terraform: tiles -> [ { "chart": { "layout": ..., "definition": ... } } ]
ということで、ダッシュボードのTerraform化は、Terraformのimportブロックを使って、既存のリソースを抽出することで実現します。まずは、既存のダッシュボードのTerraform定義をimportします。
import {
provider = konnect-beta
to = konnect_dashboard.example_dashboard
id = "<抽出予定のダッシュボードのUUID>"
}
これを用いて、リソースを抽出します。
terraform plan -generate-config-out=generated.tf
作成された generated.tf は以下のようになっており、当たり前ですがきちんとTerraformから利用可能な形になっています。
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "effa85a3-bfe0-4b48-aaa4-590efdfc46fe"
resource "konnect_dashboard" "example_dashboard" {
provider = konnect-beta
definition = {
preset_filters = [
]
tiles = [
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = null
single_value = null
timeseries_line = {
chart_title = "Total traffic over time"
type = "timeseries_line"
}
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["time"]
filters = [
]
metrics = ["request_count"]
}
llm_usage = null
}
}
layout = {
position = {
col = 0
row = 0
}
size = {
cols = 6
rows = 2
}
}
type = "chart"
}
},
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = {
chart_title = "Top gateway services by requests"
stacked = true
type = "horizontal_bar"
}
single_value = null
timeseries_line = null
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["gateway_service"]
filters = [
{
field = "gateway_service"
operator = "not_empty"
},
]
metrics = ["request_count"]
}
llm_usage = null
}
}
layout = {
position = {
col = 0
row = 2
}
size = {
cols = 2
rows = 2
}
}
type = "chart"
}
},
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = {
chart_title = "Top routes by requests"
stacked = true
type = "horizontal_bar"
}
single_value = null
timeseries_line = null
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["route"]
filters = [
{
field = "route"
operator = "not_empty"
},
]
metrics = ["request_count"]
}
llm_usage = null
}
}
layout = {
position = {
col = 2
row = 2
}
size = {
cols = 2
rows = 2
}
}
type = "chart"
}
},
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = {
chart_title = "Top consumers by requests"
stacked = true
type = "horizontal_bar"
}
single_value = null
timeseries_line = null
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["consumer"]
filters = [
{
field = "consumer"
operator = "not_empty"
},
]
metrics = ["request_count"]
}
llm_usage = null
}
}
layout = {
position = {
col = 4
row = 2
}
size = {
cols = 2
rows = 2
}
}
type = "chart"
}
},
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = null
single_value = null
timeseries_line = {
chart_title = "Latency breakdown over time"
type = "timeseries_line"
}
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["time"]
filters = [
]
metrics = ["response_latency_p99", "response_latency_p95", "response_latency_p50"]
}
llm_usage = null
}
}
layout = {
position = {
col = 0
row = 4
}
size = {
cols = 3
rows = 2
}
}
type = "chart"
}
},
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = null
single_value = null
timeseries_line = {
chart_title = "Kong vs upstream latency over time"
type = "timeseries_line"
}
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["time"]
filters = [
]
metrics = ["upstream_latency_p99", "kong_latency_p99"]
}
llm_usage = null
}
}
layout = {
position = {
col = 3
row = 4
}
size = {
cols = 3
rows = 2
}
}
type = "chart"
}
},
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = {
chart_title = "Slowest gateway services (average)"
stacked = true
type = "horizontal_bar"
}
single_value = null
timeseries_line = null
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["gateway_service"]
filters = [
{
field = "gateway_service"
operator = "not_empty"
},
]
metrics = ["response_latency_average"]
}
llm_usage = null
}
}
layout = {
position = {
col = 0
row = 6
}
size = {
cols = 2
rows = 2
}
}
type = "chart"
}
},
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = {
chart_title = "Slowest routes (average)"
stacked = true
type = "horizontal_bar"
}
single_value = null
timeseries_line = null
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["route"]
filters = [
{
field = "route"
operator = "not_empty"
},
]
metrics = ["response_latency_average"]
}
llm_usage = null
}
}
layout = {
position = {
col = 2
row = 6
}
size = {
cols = 2
rows = 2
}
}
type = "chart"
}
},
{
chart = {
definition = {
chart = {
choropleth_map = null
donut = null
horizontal_bar = {
chart_title = "Slowest consumers (average)"
stacked = true
type = "horizontal_bar"
}
single_value = null
timeseries_line = null
}
query = {
api_usage = {
datasource = "api_usage"
dimensions = ["consumer"]
filters = [
{
field = "consumer"
operator = "not_empty"
},
]
metrics = ["response_latency_average"]
}
llm_usage = null
}
}
layout = {
position = {
col = 4
row = 6
}
size = {
cols = 2
rows = 2
}
}
type = "chart"
}
},
]
}
name = "Quick summary dashboard"
}
これでGUIから作成したダッシュボードをTerraformの管理下へ置くことができるようになります。ご参考までに 🖐️
Discussion