example(telemetry-router): build hub and spoke architecture for logs #30
16 changed files with 784 additions and 1 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
### Terraform template
|
||||
.### Terraform template
|
||||
# Local .terraform directories
|
||||
**/.terraform/*
|
||||
|
||||
|
|
@ -71,3 +71,4 @@ keys
|
|||
|
||||
### K8s
|
||||
.kubeconfig
|
||||
/examples/telemetry-router-hub-spoke-setup/scripts/downloads/
|
||||
|
|
|
|||
44
examples/telemetry-router-hub-spoke-setup/.terraform.lock.hcl
generated
Normal file
44
examples/telemetry-router-hub-spoke-setup/.terraform.lock.hcl
generated
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/time" {
|
||||
version = "0.14.0"
|
||||
hashes = [
|
||||
"h1:/hlxsUpuN/lvPTNL9+NyVGsOyRsK5NsxwFMsj5CdOp4=",
|
||||
"zh:12abfd6b800e4d7fa6db7310dec8ffd440b31993861ef188c7ed5260b3073937",
|
||||
"zh:23005521e800bb19e1597bf755c5f70d675d30b685d4255001ed5fa47d9df3f1",
|
||||
"zh:2fea249b582ae97cd1cc10385187ea50993bb47c28cc5df0305e57ceaabf0a10",
|
||||
"zh:322018d3b987b7aad08697178029a2bb667bed699e88328f0c89c52a2fd41341",
|
||||
"zh:32a08e98fce2d273cb9b2c89d6c54727cc9f0a32e15bfd896be4e02cc6b48f95",
|
||||
"zh:3db89aabd0e619616bd4b0f8b373a7586dfe60feffcea12a84a0bdbc445714b3",
|
||||
"zh:7488f56c81d742dc020f29063626c8f07ca188aa97be61e7307e8d62397020a2",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:7cb4067f2e7559b13f7562ef722f948950901eb37834873e98360ab28f66e9d7",
|
||||
"zh:9d552c8345f61e1b7db8e725144981345f18ac1014d58d6f5ddf0928a195fffb",
|
||||
"zh:a8e69fb6b97fc9d86fb19a9f4d42abe33c4a68e700b15387ce2e17d2b9934bed",
|
||||
"zh:aeeb900eb8dd0f790c60ea5c0e0c8d42bd6e4a54f391681d4decca15b544394b",
|
||||
"zh:c239c619101a8c95e1f14061eb973c57a8d15fa0e68878ced5bbd76858ee5b79",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/stackitcloud/stackit" {
|
||||
version = "0.98.0"
|
||||
constraints = ">= 0.95.0"
|
||||
hashes = [
|
||||
"h1:/FB0wBnvmjumjykX+j90kSck6LMScDaYo1STO5Vp/kw=",
|
||||
"zh:031028340fbaeeb5c4c6b1d5c6d6287a70cf253cfb89f04d462a1c0ab6237ffc",
|
||||
"zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f",
|
||||
"zh:0eee18f9a262fa58966c960f1f0863eed92cd953d0f0306ecc456b58cc2911f8",
|
||||
"zh:1646966ebac0eb5d6c78ac5aa1528921d7a635f14d81300463a402c55e33cfd3",
|
||||
"zh:5374ab9e5e6d837787b4f18bcf0125a1bf3ee2da40c022cc7695d6879fed111b",
|
||||
"zh:6a5b9e1307055f8d358373da625ffcb4d77ec44f260d14473b10e5777380765e",
|
||||
"zh:6c90090504474695ab7290d64386dd988f4fb65c90c74c9cf3a6da6226ae8a70",
|
||||
"zh:8317218828f29be95ce712863646dc8968e146ec14e5ab258cb1e8f8b649245b",
|
||||
"zh:9eef08e4fb7a75760f9dc8a422446f19a210ebf8177dd5aeb97444295f0120cf",
|
||||
"zh:9f2147eee63feae75b96f17f3b3ebab8a29cd7164cdd08eb2bb871e5c425a77f",
|
||||
"zh:b63ea754eea233292fb73d87a9810104da2bd347abf2ca0da44ac76591dcdddb",
|
||||
"zh:de60bd928828a836e446f9f89e7a3bfc4e6dd73bac6827914087b34e4ad0c978",
|
||||
"zh:f22d295b2e4e94ae1566e20fd752825e008a62250cf7243f1161c0bf4e986518",
|
||||
"zh:f7e57bc7be2cc016983ff3ad50d2733b85e90bfaa7aa9e2192563dc9d422fb07",
|
||||
]
|
||||
}
|
||||
33
examples/telemetry-router-hub-spoke-setup/010-provider.tf
Normal file
33
examples/telemetry-router-hub-spoke-setup/010-provider.tf
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
stackit = {
|
||||
source = "stackitcloud/stackit"
|
||||
version = ">=0.95.0"
|
||||
}
|
||||
time = {
|
||||
source = "hashicorp/time"
|
||||
version = "~> 0.11"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "stackit" {
|
||||
default_region = var.stackit_region
|
||||
service_account_key_path = var.stackit_service_account_key_path
|
||||
enable_beta_resources = true
|
||||
experiments = ["iam"]
|
||||
}
|
||||
30
examples/telemetry-router-hub-spoke-setup/020-variables.tf
Normal file
30
examples/telemetry-router-hub-spoke-setup/020-variables.tf
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
variable "stackit_org_id" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_region" {
|
||||
type = string
|
||||
default = "eu01"
|
||||
}
|
||||
|
||||
variable "stackit_service_account_key_path" {
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "stackit_owner_email" {
|
||||
type = string
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resource "stackit_resourcemanager_folder" "this" {
|
||||
name = "telemetry-router-link-test"
|
||||
owner_email = var.stackit_owner_email
|
||||
parent_container_id = var.stackit_org_id
|
||||
}
|
||||
|
||||
resource "stackit_resourcemanager_project" "telemetry_hub" {
|
||||
parent_container_id = stackit_resourcemanager_folder.this.container_id
|
||||
name = "telemetry_hub"
|
||||
owner_email = var.stackit_owner_email
|
||||
}
|
||||
|
||||
resource "stackit_resourcemanager_project" "telemetry_spoke1" {
|
||||
parent_container_id = stackit_resourcemanager_folder.this.container_id
|
||||
name = "telemetry_spoke1"
|
||||
owner_email = var.stackit_owner_email
|
||||
}
|
||||
|
||||
resource "stackit_resourcemanager_project" "telemetry_spoke2" {
|
||||
parent_container_id = stackit_resourcemanager_folder.this.container_id
|
||||
name = "telemetry_spoke2"
|
||||
owner_email = var.stackit_owner_email
|
||||
}
|
||||
|
||||
resource "stackit_resourcemanager_project" "telemetry_spoke3" {
|
||||
parent_container_id = stackit_resourcemanager_folder.this.container_id
|
||||
name = "telemetry_spoke3"
|
||||
owner_email = var.stackit_owner_email
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resource "stackit_observability_instance" "this" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
name = "telemetry_hub"
|
||||
plan_name = "Observability-Large-EU01"
|
||||
alert_config = null
|
||||
acl = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
resource "stackit_observability_credential" "router_ingest" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
instance_id = stackit_observability_instance.this.instance_id
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
resource "stackit_logs_instance" "this" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
region = "eu01"
|
||||
display_name = "telemetry_hub"
|
||||
retention_days = 30
|
||||
acl = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
resource "stackit_logs_access_token" "router_ingest" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
instance_id = stackit_logs_instance.this.instance_id
|
||||
display_name = "router-ingest-token"
|
||||
permissions = ["write", "read"]
|
||||
}
|
||||
33
examples/telemetry-router-hub-spoke-setup/042-s3-bucket.tf
Normal file
33
examples/telemetry-router-hub-spoke-setup/042-s3-bucket.tf
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Create an S3 Bucket for log archiving
|
||||
resource "stackit_objectstorage_bucket" "log_archive" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
name = "telemetry-log-archive"
|
||||
}
|
||||
|
||||
# Create a Credentials Group for Object Storage
|
||||
resource "stackit_objectstorage_credentials_group" "router_group" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
name = "router-s3-group"
|
||||
depends_on = [stackit_objectstorage_bucket.log_archive]
|
||||
}
|
||||
|
||||
|
||||
# Create Credentials for the Telemetry Router to access the bucket
|
||||
resource "stackit_objectstorage_credential" "router_s3_creds" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
credentials_group_id = stackit_objectstorage_credentials_group.router_group.credentials_group_id
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Create the Telemetry Router Instance in the Hub project
|
||||
resource "stackit_telemetryrouter_instance" "hub_router" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
display_name = "hub-telemetry-router"
|
||||
description = "Central Telemetry Router for spoke projects and parent folder"
|
||||
}
|
||||
|
||||
# Create an Access Token for the Router
|
||||
# This token will be used by the links to authenticate with the router
|
||||
resource "stackit_telemetryrouter_access_token" "hub_router_token" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
instance_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
display_name = "hub-router-link-token"
|
||||
}
|
||||
|
||||
# Create a Destination for the Router to send all telemetry data to the central Observability instance
|
||||
resource "stackit_telemetryrouter_destination" "observability_destination" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
instance_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
display_name = "observability-dest"
|
||||
config = {
|
||||
config_type = "OpenTelemetry"
|
||||
opentelemetry = {
|
||||
# Obs-stack has https:// in the attribute
|
||||
uri = stackit_observability_instance.this.otlp_http_logs_url
|
||||
basic_auth = {
|
||||
username = stackit_observability_credential.router_ingest.username
|
||||
password = stackit_observability_credential.router_ingest.password
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a Destination for the Router to send filtered logs to the central Logs instance
|
||||
# We only want logs from the 'service-account' service
|
||||
resource "stackit_telemetryrouter_destination" "logs_destination" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
instance_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
display_name = "logs-dest"
|
||||
config = {
|
||||
config_type = "OpenTelemetry"
|
||||
filter = {
|
||||
attributes = [{
|
||||
key = "service.name"
|
||||
level = "logRecord"
|
||||
matcher = "="
|
||||
values = ["service-account"]
|
||||
}]
|
||||
}
|
||||
opentelemetry = {
|
||||
# Prepend https:// as the OTLP URI must have a protocol
|
||||
uri = "https://${stackit_logs_instance.this.ingest_otlp_url}"
|
||||
bearer_token = stackit_logs_access_token.router_ingest.access_token
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a Destination for the Router to archive all data in S3
|
||||
resource "stackit_telemetryrouter_destination" "s3_archive" {
|
||||
project_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
instance_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
display_name = "s3-log-archive"
|
||||
config = {
|
||||
config_type = "S3"
|
||||
s3 = {
|
||||
access_key = {
|
||||
id = stackit_objectstorage_credential.router_s3_creds.access_key
|
||||
secret = stackit_objectstorage_credential.router_s3_creds.secret_access_key
|
||||
}
|
||||
bucket = stackit_objectstorage_bucket.log_archive.name
|
||||
endpoint = regex("^https://[^/]+", stackit_objectstorage_bucket.log_archive.url_path_style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Link Hub Project to the Hub Router
|
||||
# NOTE: The existence of a Telemetry Router in a project DOES NOT automatically link that project's logs.
|
||||
# Every project (including the hub) must have an explicit Telemetry Link to forward its logs to a router.
|
||||
resource "stackit_telemetrylink" "hub_link" {
|
||||
resource_type = "project"
|
||||
resource_id = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
display_name = "hub-to-hub-link"
|
||||
telemetry_router_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
access_token = stackit_telemetryrouter_access_token.hub_router_token.access_token
|
||||
}
|
||||
|
||||
# Link Spoke Project 1 to the Hub Router
|
||||
resource "stackit_telemetrylink" "spoke1_link" {
|
||||
resource_type = "project"
|
||||
resource_id = stackit_resourcemanager_project.telemetry_spoke1.project_id
|
||||
display_name = "spoke1-to-hub-link"
|
||||
telemetry_router_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
access_token = stackit_telemetryrouter_access_token.hub_router_token.access_token
|
||||
}
|
||||
|
||||
# Link Spoke Project 2 to the Hub Router
|
||||
resource "stackit_telemetrylink" "spoke2_link" {
|
||||
resource_type = "project"
|
||||
resource_id = stackit_resourcemanager_project.telemetry_spoke2.project_id
|
||||
display_name = "spoke2-to-hub-link"
|
||||
telemetry_router_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
access_token = stackit_telemetryrouter_access_token.hub_router_token.access_token
|
||||
}
|
||||
|
||||
# Link Spoke Project 3 to the Hub Router
|
||||
resource "stackit_telemetrylink" "spoke3_link" {
|
||||
resource_type = "project"
|
||||
resource_id = stackit_resourcemanager_project.telemetry_spoke3.project_id
|
||||
display_name = "spoke3-to-hub-link"
|
||||
telemetry_router_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
access_token = stackit_telemetryrouter_access_token.hub_router_token.access_token
|
||||
}
|
||||
|
||||
# Link the entire Folder to the Hub Router
|
||||
# This allows telemetry data from all projects within the folder (if configured) to be routed via the hub router
|
||||
resource "stackit_telemetrylink" "folder_link" {
|
||||
resource_type = "folder"
|
||||
resource_id = stackit_resourcemanager_folder.this.folder_id
|
||||
display_name = "folder-to-hub-link"
|
||||
telemetry_router_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
access_token = stackit_telemetryrouter_access_token.hub_router_token.access_token
|
||||
}
|
||||
|
||||
# Link the entire Organization to the Hub Router
|
||||
# This is used to forward organization-level audit logs to the central router
|
||||
resource "stackit_telemetrylink" "org_link" {
|
||||
resource_type = "organization"
|
||||
resource_id = var.stackit_org_id
|
||||
display_name = "org-to-hub-link"
|
||||
telemetry_router_id = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
access_token = stackit_telemetryrouter_access_token.hub_router_token.access_token
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This file creates resources that are rotated every minute.
|
||||
# The frequent rotation of credentials triggers audit logs in each project,
|
||||
# allowing us to verify that the Telemetry Router and Links are working as expected.
|
||||
|
||||
resource "time_rotating" "minute" {
|
||||
rotation_minutes = 1
|
||||
}
|
||||
|
||||
locals {
|
||||
projects = {
|
||||
hub = stackit_resourcemanager_project.telemetry_hub.project_id
|
||||
spoke1 = stackit_resourcemanager_project.telemetry_spoke1.project_id
|
||||
spoke2 = stackit_resourcemanager_project.telemetry_spoke2.project_id
|
||||
spoke3 = stackit_resourcemanager_project.telemetry_spoke3.project_id
|
||||
}
|
||||
}
|
||||
|
||||
# Create a bucket in each project
|
||||
resource "stackit_objectstorage_bucket" "log_gen" {
|
||||
for_each = local.projects
|
||||
project_id = each.value
|
||||
name = "log-gen-bucket-${each.key}"
|
||||
}
|
||||
|
||||
# Create a credentials group in each project
|
||||
resource "stackit_objectstorage_credentials_group" "log_gen" {
|
||||
for_each = local.projects
|
||||
project_id = each.value
|
||||
name = "log-gen-group-${each.key}"
|
||||
|
||||
depends_on = [stackit_objectstorage_bucket.log_gen]
|
||||
}
|
||||
|
||||
# Create a credential in each project and rotate it every minute
|
||||
resource "stackit_objectstorage_credential" "log_gen" {
|
||||
for_each = local.projects
|
||||
project_id = each.value
|
||||
credentials_group_id = stackit_objectstorage_credentials_group.log_gen[each.key].credentials_group_id
|
||||
|
||||
# This map forces recreation of the credential whenever the time_rotating resource rotates
|
||||
rotate_when_changed = {
|
||||
rotation_id = time_rotating.minute.id
|
||||
}
|
||||
}
|
||||
|
||||
# Create a service account in each project to generate more IAM-related audit logs
|
||||
resource "stackit_service_account" "log_gen" {
|
||||
for_each = local.projects
|
||||
project_id = each.value
|
||||
name = "log-gen-sa-${each.key}"
|
||||
}
|
||||
|
||||
|
||||
resource "stackit_service_account_key" "log_gen" {
|
||||
for_each = local.projects
|
||||
project_id = each.value
|
||||
service_account_email = stackit_service_account.log_gen[each.key].email
|
||||
|
||||
rotate_when_changed = {
|
||||
rotation = time_rotating.minute.id
|
||||
}
|
||||
}
|
||||
|
||||
# Assign the 'reader' role to each service account (using IAM experimental resources)
|
||||
resource "stackit_authorization_project_role_assignment" "log_gen" {
|
||||
for_each = local.projects
|
||||
resource_id = each.value
|
||||
role = "reader"
|
||||
subject = stackit_service_account.log_gen[each.key].email
|
||||
}
|
||||
79
examples/telemetry-router-hub-spoke-setup/100-outputs.tf
Normal file
79
examples/telemetry-router-hub-spoke-setup/100-outputs.tf
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
output "telemetry_router_id" {
|
||||
description = "The ID of the central Telemetry Router"
|
||||
value = stackit_telemetryrouter_instance.hub_router.instance_id
|
||||
}
|
||||
|
||||
output "telemetry_router_uri" {
|
||||
description = "The OTLP ingest URI of the central Telemetry Router"
|
||||
value = stackit_telemetryrouter_instance.hub_router.uri
|
||||
}
|
||||
|
||||
output "spoke1_link_id" {
|
||||
description = "The ID of the Telemetry Link for Spoke Project 1"
|
||||
value = stackit_telemetrylink.spoke1_link.id
|
||||
}
|
||||
|
||||
output "spoke2_link_id" {
|
||||
description = "The ID of the Telemetry Link for Spoke Project 2"
|
||||
value = stackit_telemetrylink.spoke2_link.id
|
||||
}
|
||||
|
||||
output "spoke3_link_id" {
|
||||
description = "The ID of the Telemetry Link for Spoke Project 3"
|
||||
value = stackit_telemetrylink.spoke3_link.id
|
||||
}
|
||||
|
||||
output "folder_link_id" {
|
||||
description = "The ID of the Telemetry Link for the parent Folder"
|
||||
value = stackit_telemetrylink.folder_link.id
|
||||
}
|
||||
|
||||
output "org_link_id" {
|
||||
description = "The ID of the Telemetry Link for the Organization"
|
||||
value = stackit_telemetrylink.org_link.id
|
||||
}
|
||||
|
||||
output "observability_logs_ingest_url" {
|
||||
description = "The OTLP HTTP logs ingest URL for the Observability instance"
|
||||
value = stackit_observability_instance.this.otlp_http_logs_url
|
||||
}
|
||||
|
||||
output "logs_ingest_url" {
|
||||
description = "The OTLP ingest URL for the Logs instance"
|
||||
value = "https://${stackit_logs_instance.this.ingest_otlp_url}"
|
||||
}
|
||||
|
||||
output "s3_archive_bucket" {
|
||||
description = "The name of the S3 bucket used for log archiving"
|
||||
value = stackit_objectstorage_bucket.log_archive.name
|
||||
}
|
||||
|
||||
output "s3_access_key" {
|
||||
description = "The S3 access key for the log archive bucket"
|
||||
value = stackit_objectstorage_credential.router_s3_creds.access_key
|
||||
}
|
||||
|
||||
output "s3_secret_key" {
|
||||
description = "The S3 secret access key for the log archive bucket"
|
||||
value = stackit_objectstorage_credential.router_s3_creds.secret_access_key
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
output "s3_endpoint" {
|
||||
description = "The S3 endpoint for the log archive bucket"
|
||||
value = regex("^https://[^/]+", stackit_objectstorage_bucket.log_archive.url_path_style)
|
||||
}
|
||||
9
examples/telemetry-router-hub-spoke-setup/MAINTAINERS.md
Normal file
9
examples/telemetry-router-hub-spoke-setup/MAINTAINERS.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Maintainers
|
||||
|
||||
General maintainers:
|
||||
|
||||
- Mauritz Uphoff (mauritz.uphoff@digits.schwarz)
|
||||
|
||||
This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis.
|
||||
For questions, issues, or feature requests, please email general maintainers.
|
||||
Please include the BP name and version in your request. We will track your request as an issue.
|
||||
126
examples/telemetry-router-hub-spoke-setup/README.md
Normal file
126
examples/telemetry-router-hub-spoke-setup/README.md
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# Telemetry Router: Hub-and-Spoke Setup
|
||||
|
||||
This example demonstrates how to use the **STACKIT Telemetry Router** to centralize observability data across multiple projects, folders, and even the entire organization.
|
||||
|
||||
> ⚠️⚠️⚠️ **A Telemetry Router DOES NOT replace a Telemetry Link.** ⚠️⚠️⚠️\
|
||||
> Creating a Router in a project only provides the ingestion endpoint. To actually forward logs from that same project (or any other project) to the router, you **MUST** create an explicit `stackit_telemetrylink`. Every project in your hierarchy requires its own link to participate in the telemetry routing.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph Organization [STACKIT Organization]
|
||||
OrgLink([Link: org-to-hub-link])
|
||||
|
||||
subgraph ParentFolder [Folder: telemetry-router-link-test]
|
||||
FolderLink([Link: folder-to-hub-link])
|
||||
|
||||
subgraph HubProject [Project: telemetry_hub]
|
||||
Router{Router: hub-telemetry-router}
|
||||
HubLink([Link: hub-to-hub-link])
|
||||
Obs[Observability Instance]
|
||||
Logs[Logs Instance]
|
||||
S3[S3 Log Archive]
|
||||
|
||||
Router -->|OTLP| Obs
|
||||
Router -->|OTLP| Logs
|
||||
Router -->|S3| S3
|
||||
end
|
||||
|
||||
subgraph Spoke1 [Project: telemetry_spoke1]
|
||||
Link1([Link: spoke1-to-hub-link])
|
||||
end
|
||||
|
||||
subgraph Spoke2 [Project: telemetry_spoke2]
|
||||
Link2([Link: spoke2-to-hub-link])
|
||||
end
|
||||
|
||||
subgraph Spoke3 [Project: telemetry_spoke3]
|
||||
Link3([Link: spoke3-to-hub-link])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
%% Connections from Links to Router
|
||||
OrgLink -.->|Forward Organization Logs| Router
|
||||
FolderLink -.->|Forward Folder Logs| Router
|
||||
HubLink -.->|Forward Hub Logs| Router
|
||||
Link1 -.->|Forward Spoke Logs| Router
|
||||
Link2 -.->|Forward Spoke Logs| Router
|
||||
Link3 -.->|Forward Spoke Logs| Router
|
||||
|
||||
style Router fill:#f9f,stroke:#333,stroke-width:4px
|
||||
style OrgLink fill:#bbf,stroke:#333
|
||||
style FolderLink fill:#bbf,stroke:#333
|
||||
style HubLink fill:#bbf,stroke:#333
|
||||
style Link1 fill:#bbf,stroke:#333
|
||||
style Link2 fill:#bbf,stroke:#333
|
||||
style Link3 fill:#bbf,stroke:#333
|
||||
```
|
||||
|
||||
## What this setup does
|
||||
|
||||
1. **Centralizes Telemetry**: Creates a **Hub Project** that hosts a central Telemetry Router instance.
|
||||
2. **Connects the Hierarchy**: Uses **Telemetry Links** at three different levels:
|
||||
- **Organization Level**: Forwards organization-wide audit logs to the central router.
|
||||
- **Folder Level**: Ensures all telemetry from a specific folder and its sub-projects is routed to the Hub.
|
||||
- **Project Level**: Connects individual "Spoke" projects and the **Hub Project itself** to the Router.
|
||||
3. **Broadcasts & Filters Data**:
|
||||
- **Observability Destination**: All data is sent to a `stackit_observability_instance`.
|
||||
- **Logs Destination (Filtered)**: Only logs from the **`service-account`** service are forwarded to the `stackit_logs_instance`. This demonstrates how to filter for specific high-value audit trails (like IAM actions).
|
||||
- **S3 Archiving**: All data is also archived in a **STACKIT Object Storage (S3)** bucket for long-term retention.
|
||||
4. **Generates Continuous Logs**: To demonstrate the setup, this example includes a **Log Generator** (`070-log-generator.tf`). It creates S3 credentials in every project and rotates them **every minute**. These frequent administrative actions trigger continuous Audit Logs, which you should see appearing in your Observability and Logs instances.
|
||||
5. **Handles Authentication**:
|
||||
- Uses a **Router Access Token** for the links to connect.
|
||||
- Uses **Credentials/Access Tokens** for the router to push data to the backend Observability and Logs instances via OTLP.
|
||||
|
||||
## Resource Architecture
|
||||
|
||||
- **1 Organization**: Linked via an Org-level Telemetry Link.
|
||||
- **1 Folder**: Contains all projects and is linked via a Folder-level Link.
|
||||
- **1 Hub Project**: Contains the Router, Observability, Logs, and S3 Bucket instances. **⚠️⚠️⚠️ Crucially, it is also linked to its own Router.⚠️⚠️⚠️**
|
||||
- **3 Spoke Projects**: Connected to the Hub via individual Project-level Links.
|
||||
|
||||
## How to use
|
||||
|
||||
1. Set your variables in a `terraform.tfvars` file (Org ID, Owner Email, etc.).
|
||||
2. Initialize and apply:
|
||||
```bash
|
||||
terraform init
|
||||
terraform apply
|
||||
```
|
||||
3. Check the **outputs** for the Router URI and all Link IDs (Org, Folder, and Projects) to verify the connection.
|
||||
|
||||
## Post-Deployment: Monitoring & Retrieval
|
||||
|
||||
To avoid external dependencies during deployment, all scripts are located in the `scripts/` directory and should be run manually from within this folder.
|
||||
|
||||
### Monitoring S3 Archive
|
||||
|
||||
To check how many log objects are currently archived in S3:
|
||||
|
||||
```bash
|
||||
./scripts/count-s3-items.sh
|
||||
```
|
||||
|
||||
This script retrieves the necessary credentials from the Terraform state and uses the AWS CLI to count the objects.
|
||||
|
||||
### Log Retrieval, Extraction & Beautification
|
||||
|
||||
To download, automatically unzip, and beautify all archived logs from S3:
|
||||
|
||||
```bash
|
||||
./scripts/download-s3-logs.sh
|
||||
```
|
||||
|
||||
The script will:
|
||||
|
||||
1. Download all logs from S3.
|
||||
2. Unzip all `.gz` files.
|
||||
3. Format all JSON files for better readability (using `jq`).
|
||||
|
||||
The logs will be saved in the `scripts/downloads/` directory (which is ignored by Git).
|
||||
|
||||
---
|
||||
|
||||
_Note: This service is currently in beta. `enable_beta_resources = true` is required in the provider configuration._
|
||||
31
examples/telemetry-router-hub-spoke-setup/scripts/count-s3-items.sh
Executable file
31
examples/telemetry-router-hub-spoke-setup/scripts/count-s3-items.sh
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
#!/bin/bash
|
||||
# This script counts objects in a STACKIT S3 bucket.
|
||||
# It automatically retrieves configuration and credentials from the terraform state.
|
||||
# Requirements: aws cli, terraform, jq installed
|
||||
|
||||
set -e
|
||||
|
||||
echo "[*] Retrieving S3 configuration from Terraform state..."
|
||||
|
||||
# Check if terraform state is available in current directory
|
||||
if ! terraform output -json > /dev/null 2>&1; then
|
||||
echo "Error: Could not read terraform output. Make sure you are in the terraform directory and have run 'terraform apply' first."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Retrieve values from terraform output
|
||||
ACCESS_KEY=$(terraform output -raw s3_access_key)
|
||||
SECRET_KEY=$(terraform output -raw s3_secret_key)
|
||||
ENDPOINT=$(terraform output -raw s3_endpoint)
|
||||
BUCKET=$(terraform output -raw s3_archive_bucket)
|
||||
|
||||
# Configure AWS CLI environment
|
||||
export AWS_ACCESS_KEY_ID="$ACCESS_KEY"
|
||||
export AWS_SECRET_ACCESS_KEY="$SECRET_KEY"
|
||||
export AWS_DEFAULT_REGION="eu01"
|
||||
|
||||
# Count objects using aws cli
|
||||
# We use xargs to trim whitespace from wc output
|
||||
COUNT=$(aws --endpoint-url "$ENDPOINT" s3 ls "s3://$BUCKET" --recursive | grep -v "^$" | wc -l | xargs)
|
||||
|
||||
echo "[+] Current object count in s3://$BUCKET: $COUNT"
|
||||
57
examples/telemetry-router-hub-spoke-setup/scripts/download-s3-logs.sh
Executable file
57
examples/telemetry-router-hub-spoke-setup/scripts/download-s3-logs.sh
Executable file
|
|
@ -0,0 +1,57 @@
|
|||
#!/bin/bash
|
||||
# This script downloads all objects from the STACKIT S3 archive bucket to a local directory,
|
||||
# extracts compressed log files (.gz), and beautifies JSON content.
|
||||
#
|
||||
# It automatically retrieves configuration and credentials from the terraform state.
|
||||
# Requirements: aws cli, terraform, jq, gunzip installed
|
||||
|
||||
set -e
|
||||
|
||||
echo "[*] Retrieving S3 configuration from Terraform state..."
|
||||
|
||||
# Check if terraform state is available (script should be run from the terraform directory)
|
||||
if ! terraform output -json > /dev/null 2>&1; then
|
||||
echo "Error: Could not read terraform output. Make sure you have run 'terraform apply' first and are calling this script from the terraform directory."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Retrieve values from terraform output
|
||||
ACCESS_KEY=$(terraform output -raw s3_access_key)
|
||||
SECRET_KEY=$(terraform output -raw s3_secret_key)
|
||||
ENDPOINT=$(terraform output -raw s3_endpoint)
|
||||
BUCKET=$(terraform output -raw s3_archive_bucket)
|
||||
|
||||
# Get script directory to create downloads folder there
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
DOWNLOAD_DIR="$SCRIPT_DIR/downloads"
|
||||
|
||||
# Create download directory
|
||||
mkdir -p "$DOWNLOAD_DIR"
|
||||
|
||||
# Configure AWS CLI environment
|
||||
export AWS_ACCESS_KEY_ID="$ACCESS_KEY"
|
||||
export AWS_SECRET_ACCESS_KEY="$SECRET_KEY"
|
||||
export AWS_DEFAULT_REGION="eu01"
|
||||
|
||||
echo "[*] Starting download from s3://$BUCKET to $DOWNLOAD_DIR..."
|
||||
echo "[*] Endpoint: $ENDPOINT"
|
||||
|
||||
# Use sync to download all files efficiently
|
||||
aws --endpoint-url "$ENDPOINT" s3 sync "s3://$BUCKET" "$DOWNLOAD_DIR"
|
||||
|
||||
echo "[*] Extracting compressed log files (.gz)..."
|
||||
# Find all .gz files in the download directory and unzip them
|
||||
find "$DOWNLOAD_DIR" -name "*.gz" -exec gunzip -f {} +
|
||||
|
||||
echo "[*] Beautifying JSON log files..."
|
||||
# Find all files (now uncompressed) and try to beautify them with jq if they contain JSON
|
||||
# We use a temporary file to perform in-place beautification
|
||||
find "$DOWNLOAD_DIR" -type f ! -name "*.gz" | while read -r file; do
|
||||
if jq . "$file" > "$file.tmp" 2>/dev/null; then
|
||||
mv "$file.tmp" "$file"
|
||||
else
|
||||
rm -f "$file.tmp"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "[+] Download, extraction, and beautification complete! Files are located in $DOWNLOAD_DIR"
|
||||
Loading…
Add table
Add a link
Reference in a new issue