diff --git a/modules/services/output.tf b/modules/services/output.tf index 53816ba..c63d091 100644 --- a/modules/services/output.tf +++ b/modules/services/output.tf @@ -1,9 +1,9 @@ output "authentik_data" { value = { for key in keys(authentik_application.service_applications) : key => { - "base_url" = "${var.authentik_url}/application/o/${authentik_application.service_applications[key].slug}" - "client_id" = authentik_provider_oauth2.service_providers[key].client_id - "client_secret" = authentik_provider_oauth2.service_providers[key].client_secret + base_url = "${var.authentik_url}/application/o/${authentik_application.service_applications[key].slug}" + client_id = authentik_provider_oauth2.service_providers[key].client_id + client_secret = authentik_provider_oauth2.service_providers[key].client_secret } } sensitive = true @@ -12,19 +12,28 @@ output "authentik_data" { output "postgresql_data" { value = { for key in keys(postgresql_database.service_databases) : key => { - "user" = postgresql_role.service_roles[key].name - "pass" = postgresql_role.service_roles[key].password - "database" = postgresql_database.service_databases[key].name + user = postgresql_role.service_roles[key].name + pass = postgresql_role.service_roles[key].password + database = postgresql_database.service_databases[key].name } } sensitive = true } +output "postgresql_metrics_collector" { + value = { + user = postgresql_role.metrics_collector_role.name + pass = postgresql_role.metrics_collector_role.password + database = postgresql_database.metrics_collector_database.name + } + sensitive = true +} + output "mailcow_data" { value = { for key in keys(mailcow_mailbox.services) : key => { - "address" = mailcow_mailbox.services[key].address - "password" = mailcow_mailbox.services[key].password + address = mailcow_mailbox.services[key].address + password = mailcow_mailbox.services[key].password } } sensitive = true diff --git a/modules/services/postgresql.tf b/modules/services/postgresql.tf index ca1ab84..ee3040a 100644 --- a/modules/services/postgresql.tf +++ b/modules/services/postgresql.tf @@ -16,3 +16,21 @@ resource "postgresql_database" "service_databases" { name = each.key owner = postgresql_role.service_roles[each.key].name } + +resource "random_password" "postgresql_metrics_collector_password" { + length = 32 + special = false +} + +resource "postgresql_role" "metrics_collector_role" { + name = "metrics_collector" + login = true + password = random_password.postgresql_metrics_collector_password.result + search_path = ["postgres_exporter", "pg_catalog"] + roles = ["pg_monitor", "pg_read_all_stats"] +} + +resource "postgresql_database" "metrics_collector_database" { + name = "metrics_collector" + owner = postgresql_role.metrics_collector_role.name +} diff --git a/output.tf b/output.tf index 5b4e5ca..61c3440 100644 --- a/output.tf +++ b/output.tf @@ -27,6 +27,11 @@ output "postgresql_data" { sensitive = true } +output "postgresql_metrics_collector" { + value = module.services.postgresql_metrics_collector + sensitive = true +} + output "postgresql" { value = { "host" = var.postgresql_host diff --git a/playbooks/filter_plugins/alloy.py b/playbooks/filter_plugins/alloy.py new file mode 100644 index 0000000..72ac4c0 --- /dev/null +++ b/playbooks/filter_plugins/alloy.py @@ -0,0 +1,44 @@ +def transfer_optional_param(source, target, name, target_name=None): + if param := source.get(name): + target[target_name or name] = param + +class FilterModule(object): + def filters(self): + return { + 'services_to_alloy': self.services_to_alloy, + } + + def services_to_alloy(self, services): + result = [] + + for name, service in services.items(): + if not bool(service.get("host")): + continue + + if targets := service.get("metrics") or []: + job = { + "name": name, + "targets": [], + "scrape_interval": "60s", + } + + for target in targets: + + address = target.get("address") or service["dns"][0]['domain'] + + transfer_optional_param(target, job, "interval", "scrape_interval") + + new_target = { + "address": address, + "path": target["path"], + "instance": name + } + + transfer_optional_param(target, new_target, "instance") + transfer_optional_param(target, new_target, "job") + + job["targets"].append(new_target) + + result.append(job) + + return result diff --git a/playbooks/roles/lgtm_stack/templates/config.alloy.j2 b/playbooks/roles/lgtm_stack/templates/config.alloy.j2 index 8fc772a..950059e 100644 --- a/playbooks/roles/lgtm_stack/templates/config.alloy.j2 +++ b/playbooks/roles/lgtm_stack/templates/config.alloy.j2 @@ -3,23 +3,66 @@ logging { format = "logfmt" } +prometheus.remote_write "mimir" { + endpoint { + url = "https://{{ lgtm_stack_mimir_domain }}/api/v1/push" + } +} + prometheus.exporter.self "alloy" {} prometheus.scrape "alloy" { targets = prometheus.exporter.self.alloy.targets forward_to = [prometheus.remote_write.mimir.receiver] } -prometheus.scrape "node_exporter" { - targets = [ -{% for host_data in opentofu.hosts.values() %} - {"__address__" = "{{ host_data.fqdn_vpn }}:9100", "job" = "node_exporter"}, -{% endfor %} - ] - forward_to = [prometheus.remote_write.mimir.receiver] +prometheus.exporter.postgres "default" { + data_source_names = ["postgresql://{{ svc.postgresql_collector.user }}:{{ svc.postgresql_collector.pass }}@{{ svc.postgresql_collector.host }}:{{ svc.postgresql_collector.port }}/{{ svc.postgresql_collector.database }}?sslmode=verify-full"] + + autodiscovery { + enabled = true + } +} +prometheus.scrape "postgres" { + targets = prometheus.exporter.postgres.default.targets + forward_to = [prometheus.remote_write.mimir.receiver] } -prometheus.remote_write "mimir" { - endpoint { - url = "https://{{ lgtm_stack_mimir_domain }}/api/v1/push" - } +prometheus.scrape "node_exporter" { + targets = [ +{% for host_data in opentofu.hosts.values() %} + {"__address__" = "{{ host_data.fqdn_vpn }}:9100", "instance" = "{{ host_data.hostname }}"}, +{% endfor %} + ] + forward_to = [prometheus.remote_write.mimir.receiver] } + +prometheus.scrape "caddy" { + targets = [ +{% for host_data in opentofu.hosts.values() %} + {"__address__" = "{{ host_data.fqdn_vpn }}:2019", "instance" = "{{ host_data.hostname }}"}, +{% endfor %} + ] + forward_to = [prometheus.remote_write.mimir.receiver] +} + + +{% for job in lgtm_stack_alloy_jobs %} + +prometheus.scrape "{{ job.name }}" { + targets = [ +{% for target in job.targets %} + { + "__address__" = "{{ target.address }}", + "__metrics_path__" = "{{ target.path }}", + "__scheme__" = "https", + {% if 'job' in target %}"job" = "{{ target.job }}",{% endif %} + {% if 'instance' in target %}"instance" = "{{ target.instance }}",{% endif %} + }, +{% endfor %} + ] + + scrape_interval = "{{ job.scrape_interval }}" + forward_to = [prometheus.remote_write.mimir.receiver] +} + +{% endfor %} \ No newline at end of file diff --git a/playbooks/roles/lgtm_stack/vars/main.yml b/playbooks/roles/lgtm_stack/vars/main.yml index b8b1d48..2603252 100644 --- a/playbooks/roles/lgtm_stack/vars/main.yml +++ b/playbooks/roles/lgtm_stack/vars/main.yml @@ -3,6 +3,9 @@ lgtm_stack_domain: "{{ all_services | service_get_domain(role_name) }}" lgtm_stack_mimir_domain: mimir.serguzim.me lgtm_stack_alloy_domain: alloy.serguzim.me +lgtm_stack_alloy_jobs: "{{ all_services | services_to_alloy() }}" + + lgtm_stack_svc: domain: "{{ lgtm_stack_domain }}" port: 3000 @@ -15,6 +18,12 @@ lgtm_stack_svc: docker_host: lgtm_stack_mimir port: 9009 caddy_extra: import vpn_only + postgresql_collector: + host: "{{ postgres.host }}" + port: "{{ postgres.port }}" + user: "{{ opentofu.postgresql_metrics_collector.user }}" + pass: "{{ opentofu.postgresql_metrics_collector.pass }}" + database: "{{ opentofu.postgresql_metrics_collector.database }}" lgtm_stack_env: diff --git a/services.auto.tfvars b/services.auto.tfvars index dcec17e..678c781 100644 --- a/services.auto.tfvars +++ b/services.auto.tfvars @@ -196,6 +196,10 @@ services = { name = "forgejo_data" type = "docker" }] + # TODO: add auth stuff to alloy + #metrics = [{ + # path = "/metrics" + #}] monitoring = { url = "/api/v1/version" group = "3-services" @@ -300,6 +304,9 @@ services = { name = "influxdb_data" type = "docker" }] + metrics = [{ + path = "/metrics" + }] monitoring = { url = "/health" group = "3-services" @@ -501,6 +508,9 @@ services = { name = "ntfy_data" type = "docker" }] + metrics = [{ + path = "/metrics" + }] monitoring = { url = "/v1/health" group = "3-services" @@ -607,6 +617,9 @@ services = { name = "synapse_media_store" type = "docker" }] + metrics = [{ + path = "/_synapse/metrics" + }] monitoring = { url = "/_matrix/client/versions" group = "3-services" @@ -732,6 +745,9 @@ services = { name = "vikunja_data" type = "docker" }] + metrics = [{ + path = "/api/v1/metrics" + }] monitoring = { url = "/api/v1/info" group = "3-services" @@ -792,6 +808,10 @@ services = { alias = "woodpecker" } ] + # TODO: add auth stuff to alloy + #metrics = [{ + # path = "/metrics" + #}] monitoring = { url = "/healthz" group = "3-services" diff --git a/variables.tf b/variables.tf index cf4ebf8..271854e 100644 --- a/variables.tf +++ b/variables.tf @@ -139,6 +139,13 @@ variable "services" { interval = optional(string) conditions = optional(list(string)) })) + metrics = optional(list(object({ + path = string + address = optional(string) + instance = optional(string) + job = optional(string) + interval = optional(string) + }))) ports = optional(list(object({ description = string port = string