mirror of
https://github.com/h44z/wg-portal
synced 2025-02-26 05:49:14 +00:00
feat: Metrics for Prometheus (#309)
* feat: prometheus metrics * Added Prometheus resources support to helm chart
This commit is contained in:
parent
ae1be0e367
commit
f22a7e4a2e
@ -59,8 +59,10 @@ ENV TZ=UTC
|
||||
COPY --from=builder /build/dist/wg-portal /app/wg-portal
|
||||
# Set the Current Working Directory inside the container
|
||||
WORKDIR /app
|
||||
# by default, the web-portal is reachable on port 8888
|
||||
# Expose default ports for metrics, web and wireguard
|
||||
EXPOSE 8787/tcp
|
||||
EXPOSE 8888/tcp
|
||||
EXPOSE 51820/udp
|
||||
# the database and config file can be mounted from the host
|
||||
VOLUME [ "/app/data", "/app/config" ]
|
||||
# Command to run the executable
|
||||
|
46
README.md
46
README.md
@ -37,6 +37,7 @@ The configuration portal supports using a database (SQLite, MySQL, MsSQL or Post
|
||||
* Support for multiple WireGuard interfaces
|
||||
* Peer Expiry Feature
|
||||
* Handle route and DNS settings like wg-quick does
|
||||
* Exposes Prometheus [metrics](#metrics)
|
||||
* ~~REST API for management and client deployment~~ (coming soon)
|
||||
|
||||

|
||||
@ -79,10 +80,11 @@ The following configuration options are available:
|
||||
| ping_check_workers | statistics | 10 | Number of parallel ping checks that will be executed. |
|
||||
| ping_unprivileged | statistics | false | If set to false, the ping checks will run without root permissions (BETA). |
|
||||
| ping_check_interval | statistics | 1m | The interval time between two ping check runs. |
|
||||
| data_collection_interval | statistics | 10m | The interval between the data collection cycles. |
|
||||
| data_collection_interval | statistics | 1m | The interval between the data collection cycles. |
|
||||
| collect_interface_data | statistics | true | A flag to enable interface data collection like bytes sent and received. |
|
||||
| collect_peer_data | statistics | true | A flag to enable peer data collection like bytes sent and received, last handshake and remote endpoint address. |
|
||||
| collect_audit_data | statistics | true | If enabled, some events, like portal logins, will be logged to the database. |
|
||||
| listening_address | statistics | :8787 | The listening address of the Prometheus metric server. |
|
||||
| host | mail | 127.0.0.1 | The mail-server address. |
|
||||
| port | mail | 25 | The mail-server SMTP port. |
|
||||
| encryption | mail | none | SMTP encryption type, allowed values: none, tls, starttls. |
|
||||
@ -204,6 +206,48 @@ make build
|
||||
* [Bootstrap](https://getbootstrap.com/), for the HTML templates
|
||||
* [Vue.JS](https://vuejs.org/), for the frontend
|
||||
|
||||
## Metrics
|
||||
|
||||
Metrics are available if interface/peer statistic data collection is enabled.
|
||||
|
||||
Add following scrape job to your Prometheus config file:
|
||||
|
||||
```yaml
|
||||
# prometheus.yaml
|
||||
scrape_configs:
|
||||
- job_name: "wg-portal"
|
||||
scrape_interval: 60s
|
||||
static_configs:
|
||||
- targets: ["wg-portal:8787"]
|
||||
```
|
||||
|
||||
Exposed metrics:
|
||||
|
||||
```console
|
||||
# HELP wireguard_interface_info Interface info.
|
||||
# TYPE wireguard_interface_info gauge
|
||||
|
||||
# HELP wireguard_interface_received_bytes_total Bytes received througth the interface.
|
||||
# TYPE wireguard_interface_received_bytes_total gauge
|
||||
|
||||
# HELP wireguard_interface_sent_bytes_total Bytes sent through the interface.
|
||||
# TYPE wireguard_interface_sent_bytes_total gauge
|
||||
|
||||
# HELP wireguard_peer_info Peer info.
|
||||
# TYPE wireguard_peer_info gauge
|
||||
|
||||
# HELP wireguard_peer_received_bytes_total Bytes received from the peer.
|
||||
# TYPE wireguard_peer_received_bytes_total gauge
|
||||
|
||||
# HELP wireguard_peer_sent_bytes_total Bytes sent to the peer.
|
||||
# TYPE wireguard_peer_sent_bytes_total gauge
|
||||
|
||||
# HELP wireguard_peer_up Peer connection state (boolean: 1/0).
|
||||
# TYPE wireguard_peer_up gauge
|
||||
|
||||
# HELP wireguard_peer_last_handshake_seconds Seconds from the last handshake with the peer.
|
||||
# TYPE wireguard_peer_last_handshake_seconds gauge
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
@ -2,6 +2,11 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/app/api/core"
|
||||
handlersV0 "github.com/h44z/wg-portal/internal/app/api/v0/handlers"
|
||||
"github.com/h44z/wg-portal/internal/app/audit"
|
||||
@ -11,10 +16,6 @@ import (
|
||||
"github.com/h44z/wg-portal/internal/app/route"
|
||||
"github.com/h44z/wg-portal/internal/app/users"
|
||||
"github.com/h44z/wg-portal/internal/app/wireguard"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/adapters"
|
||||
@ -49,6 +50,8 @@ func main() {
|
||||
|
||||
mailer := adapters.NewSmtpMailRepo(cfg.Mail)
|
||||
|
||||
metricsServer := adapters.NewMetricsServer(cfg, database)
|
||||
|
||||
cfgFileSystem, err := adapters.NewFileSystemRepository(cfg.Advanced.ConfigStoragePath)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
@ -75,7 +78,7 @@ func main() {
|
||||
wireGuardManager, err := wireguard.NewWireGuardManager(cfg, eventBus, wireGuard, wgQuick, database)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard)
|
||||
statisticsCollector, err := wireguard.NewStatisticsCollector(cfg, database, wireGuard, metricsServer)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
cfgFileManager, err := configfile.NewConfigFileManager(cfg, eventBus, database, database, cfgFileSystem)
|
||||
@ -103,6 +106,7 @@ func main() {
|
||||
webSrv, err := core.NewServer(cfg, apiFrontend)
|
||||
internal.AssertNoError(err)
|
||||
|
||||
go metricsServer.Run(ctx)
|
||||
go webSrv.Run(ctx, cfg.Web.ListeningAddress)
|
||||
|
||||
// wait until context gets cancelled
|
||||
|
@ -16,7 +16,7 @@ annotations:
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.2.0
|
||||
version: 0.3.0
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
|
@ -1,6 +1,6 @@
|
||||
# wg-portal
|
||||
|
||||
  
|
||||
  
|
||||
|
||||
WireGuard Configuration Portal with LDAP, OAuth, OIDC authentication
|
||||
|
||||
@ -76,6 +76,7 @@ The [Values](#values) section lists the parameters that can be configured during
|
||||
| service.wireguard.annotations | object | `{}` | Annotations for the WireGuard service |
|
||||
| service.wireguard.type | string | `"LoadBalancer"` | Wireguard service type |
|
||||
| service.wireguard.ports | list | `[51820]` | Wireguard service ports. Exposes the WireGuard ports for created interfaces. Lowerest port is selected as start port for the first interface. Increment next port by 1 for each additional interface. |
|
||||
| service.metrics.port | int | `8787` | |
|
||||
| ingress.enabled | bool | `false` | Specifies whether an ingress resource should be created |
|
||||
| ingress.className | string | `""` | Ingress class name |
|
||||
| ingress.annotations | object | `{}` | Ingress annotations |
|
||||
@ -104,3 +105,14 @@ The [Values](#values) section lists the parameters that can be configured during
|
||||
| serviceAccount.annotations | object | `{}` | Service account annotations |
|
||||
| serviceAccount.automount | bool | `false` | Automatically mount a ServiceAccount's API credentials |
|
||||
| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template |
|
||||
| monitoring.enabled | bool | `true` | Enable Prometheus monitoring. |
|
||||
| monitoring.apiVersion | string | `"monitoring.coreos.com/v1"` | API version of the Prometheus resource. Use `azmonitoring.coreos.com/v1` for Azure Managed Prometheus. |
|
||||
| monitoring.kind | string | `"PodMonitor"` | Kind of the Prometheus resource. Could be `PodMonitor` or `ServiceMonitor`. |
|
||||
| monitoring.labels | object | `{}` | Resource labels. |
|
||||
| monitoring.annotations | object | `{}` | Resource annotations. |
|
||||
| monitoring.interval | string | `""` | Interval at which metrics should be scraped. If not specified Prometheus' global scrape interval is used. |
|
||||
| monitoring.metricRelabelings | list | `[]` | Relabelings to samples before ingestion. |
|
||||
| monitoring.relabelings | list | `[]` | Relabelings to samples before scraping. |
|
||||
| monitoring.scrapeTimeout | string | `""` | Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used. |
|
||||
| monitoring.jobLabel | string | `""` | The label to use to retrieve the job name from. |
|
||||
| monitoring.podTargetLabels | object | `{}` | Transfers labels on the Kubernetes Pod onto the target. |
|
||||
|
@ -107,3 +107,23 @@ Define hostname
|
||||
{{- (urlParse (tpl .Values.config.web.external_url .)).hostname -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
{{/*
|
||||
wg-portal.util.merge will merge two YAML templates or dict with template and output the result.
|
||||
This takes an array of three values:
|
||||
- the top context
|
||||
- the template name or dict of the overrides (destination)
|
||||
- the template name of the base (source)
|
||||
{{- include "wg-portal.util.merge" (list $ .Values.podLabels "wg-portal.selectorLabels") }}
|
||||
{{- include "wg-portal.util.merge" (list $ "wg-portal.destTemplate" "wg-portal.sourceTemplate") }}
|
||||
*/}}
|
||||
{{- define "wg-portal.util.merge" -}}
|
||||
{{- $top := first . -}}
|
||||
{{- $overrides := index . 1 -}}
|
||||
{{- $base := fromYaml (include (index . 2) $top) | default (dict) -}}
|
||||
{{- if kindIs "string" $overrides -}}
|
||||
{{- $overrides = fromYaml (include $overrides $top) | default (dict) -}}
|
||||
{{- end -}}
|
||||
{{- toYaml (merge $overrides $base) -}}
|
||||
{{- end -}}
|
||||
|
@ -6,11 +6,7 @@ metadata:
|
||||
{{- with .Values.podAnnotations }}
|
||||
{{- tpl (toYaml .) $ | nindent 4 }}
|
||||
{{- end }}
|
||||
labels:
|
||||
{{- include "wg-portal.selectorLabels" . | nindent 4 }}
|
||||
{{- with .Values.podLabels }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels: {{- include "wg-portal.util.merge" (list $ .Values.podLabels "wg-portal.selectorLabels") | nindent 4 }}
|
||||
spec:
|
||||
{{- with .Values.affinity }}
|
||||
affinity: {{- toYaml . | nindent 4 }}
|
||||
@ -36,6 +32,9 @@ spec:
|
||||
envFrom: {{- tpl (toYaml .) $ | nindent 8 }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: metrics
|
||||
containerPort: {{ .Values.service.metrics.port}}
|
||||
protocol: TCP
|
||||
- name: web
|
||||
containerPort: {{ .Values.service.web.port }}
|
||||
protocol: TCP
|
||||
|
41
deploy/helm/templates/monitoring.yaml
Normal file
41
deploy/helm/templates/monitoring.yaml
Normal file
@ -0,0 +1,41 @@
|
||||
{{- with .Values.monitoring -}}
|
||||
{{- if and .enabled ($.Capabilities.APIVersions.Has .apiVersion) -}}
|
||||
{{- $endpointsKey := (eq .kind "PodMonitor") | ternary "podMetricsEndpoints" "endpoints" -}}
|
||||
apiVersion: {{ .apiVersion }}
|
||||
kind: {{ .kind }}
|
||||
metadata:
|
||||
{{- with .annotations }}
|
||||
annotations: {{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
labels: {{- include "wg-portal.util.merge" (list $ .labels "wg-portal.labels") | nindent 4 }}
|
||||
name: {{ include "wg-portal.fullname" $ }}
|
||||
spec:
|
||||
namespaceSelector:
|
||||
matchNames:
|
||||
- {{ $.Release.Namespace }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "wg-portal.selectorLabels" $ | nindent 6 }}
|
||||
{{ $endpointsKey }}:
|
||||
- port: metrics
|
||||
path: /metrics
|
||||
{{- with .interval }}
|
||||
interval: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .metricRelabelings }}
|
||||
metricRelabelings: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .relabelings }}
|
||||
relabelings: {{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .scrapeTimeout }}
|
||||
scrapeTimeout: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .jobLabel }}
|
||||
jobLabel: {{ . }}
|
||||
{{- end }}
|
||||
{{- with .podTargetLabels }}
|
||||
podTargetLabels: {{- toYaml . | nindent 2 }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
@ -27,9 +27,12 @@ stringData:
|
||||
mail: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
|
||||
{{- with .Values.config.statistics }}
|
||||
statistics: {{- tpl (toYaml .) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
statistics:
|
||||
listening_address: :{{ .Values.service.metrics.port }}
|
||||
{{- with .Values.config.statistics }}
|
||||
{{- tpl (toYaml (omit . "listening_address")) $ | nindent 6 }}
|
||||
{{- end }}
|
||||
|
||||
web:
|
||||
listening_address: :{{ .Values.service.web.port }}
|
||||
{{- with .Values.config.web }}
|
||||
|
@ -12,3 +12,9 @@
|
||||
---
|
||||
{{ include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.wireguard "ports" $ports "name" "wireguard") }}
|
||||
{{- end -}}
|
||||
|
||||
{{- if and .Values.monitoring.enabled (eq .Values.monitoring.kind "ServiceMonitor") }}
|
||||
---
|
||||
{{- $portsMetrics := list (dict "name" "metrics" "port" .Values.service.metrics.port "protocol" "TCP" "targetPort" "metrics") -}}
|
||||
{{- include "wg-portal.service.tpl" (dict "context" . "scope" .Values.service.metrics "ports" $portsWeb "name" "metrics") }}
|
||||
{{- end -}}
|
||||
|
@ -134,6 +134,8 @@ service:
|
||||
# Increment next port by 1 for each additional interface.
|
||||
ports:
|
||||
- 51820
|
||||
metrics:
|
||||
port: 8787
|
||||
|
||||
ingress:
|
||||
# -- Specifies whether an ingress resource should be created
|
||||
@ -202,3 +204,29 @@ serviceAccount:
|
||||
# -- The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name: ''
|
||||
|
||||
monitoring:
|
||||
# -- Enable Prometheus monitoring.
|
||||
enabled: true
|
||||
# -- API version of the Prometheus resource.
|
||||
# Use `azmonitoring.coreos.com/v1` for Azure Managed Prometheus.
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
# -- Kind of the Prometheus resource.
|
||||
# Could be `PodMonitor` or `ServiceMonitor`.
|
||||
kind: PodMonitor
|
||||
# -- Resource labels.
|
||||
labels: {}
|
||||
# -- Resource annotations.
|
||||
annotations: {}
|
||||
# -- Interval at which metrics should be scraped. If not specified Prometheus' global scrape interval is used.
|
||||
interval: ''
|
||||
# -- Relabelings to samples before ingestion.
|
||||
metricRelabelings: []
|
||||
# -- Relabelings to samples before scraping.
|
||||
relabelings: []
|
||||
# -- Timeout after which the scrape is ended If not specified, the Prometheus global scrape interval is used.
|
||||
scrapeTimeout: ''
|
||||
# -- The label to use to retrieve the job name from.
|
||||
jobLabel: ''
|
||||
# -- Transfers labels on the Kubernetes Pod onto the target.
|
||||
podTargetLabels: {}
|
||||
|
11
go.mod
11
go.mod
@ -11,6 +11,7 @@ require (
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.8
|
||||
github.com/prometheus-community/pro-bing v0.4.1
|
||||
github.com/prometheus/client_golang v1.20.4
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
@ -31,6 +32,16 @@ require (
|
||||
gorm.io/gorm v1.25.12
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.55.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
|
||||
|
16
go.sum
16
go.sum
@ -32,6 +32,8 @@ github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
|
||||
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa h1:LHTHcTQiSGT7VVbI0o4wBRNQIgn917usHWOd6VAffYI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20231016080023-1a75b4708caa/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/boj/redistore v0.0.0-20180917114910-cd5dcc76aeff/go.mod h1:+RTT1BOk5P97fT2CiHkbFQwkK3mjsFAP6zCYV2aXtjw=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
|
||||
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20181103040241-659414f458e1/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
|
||||
@ -40,6 +42,8 @@ github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKz
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
@ -173,6 +177,8 @@ github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kidstuff/mongostore v0.0.0-20181113001930-e650cd85ee4b/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
@ -213,6 +219,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
|
||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
@ -224,6 +232,14 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus-community/pro-bing v0.4.1 h1:aMaJwyifHZO0y+h8+icUz0xbToHbia0wdmzdVZ+Kl3w=
|
||||
github.com/prometheus-community/pro-bing v0.4.1/go.mod h1:aLsw+zqCaDoa2RLVVSX3+UiCkBBXTMtZC3c7EkfWnAE=
|
||||
github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=
|
||||
github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
|
||||
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
|
||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/quasoft/memstore v0.0.0-20180925164028-84a050167438/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b h1:aUNXCGgukb4gtY99imuIeoh8Vr0GSwAlYxPAhqZrpFc=
|
||||
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b/go.mod h1:wTPjTepVu7uJBYgZ0SdWHQlIas582j6cn2jgk4DDdlg=
|
||||
|
161
internal/adapters/metrics.go
Normal file
161
internal/adapters/metrics.go
Normal file
@ -0,0 +1,161 @@
|
||||
package adapters
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type MetricsServer struct {
|
||||
*http.Server
|
||||
db *SqlRepo
|
||||
|
||||
ifaceInfo *prometheus.GaugeVec
|
||||
ifaceReceivedBytesTotal *prometheus.GaugeVec
|
||||
ifaceSendBytesTotal *prometheus.GaugeVec
|
||||
peerInfo *prometheus.GaugeVec
|
||||
peerIsConnected *prometheus.GaugeVec
|
||||
peerLastHandshakeSeconds *prometheus.GaugeVec
|
||||
peerReceivedBytesTotal *prometheus.GaugeVec
|
||||
peerSendBytesTotal *prometheus.GaugeVec
|
||||
}
|
||||
|
||||
// Wireguard metrics labels
|
||||
var (
|
||||
labels = []string{"interface"}
|
||||
ifaceLabels = []string{}
|
||||
peerLabels = []string{"addresses", "id", "name"}
|
||||
)
|
||||
|
||||
// NewMetricsServer returns a new prometheus server
|
||||
func NewMetricsServer(cfg *config.Config, db *SqlRepo) *MetricsServer {
|
||||
reg := prometheus.NewRegistry()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}))
|
||||
|
||||
return &MetricsServer{
|
||||
Server: &http.Server{
|
||||
Addr: cfg.Statistics.ListeningAddress,
|
||||
Handler: mux,
|
||||
},
|
||||
db: db,
|
||||
|
||||
ifaceInfo: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_interface_info",
|
||||
Help: "Interface info.",
|
||||
}, append(labels, ifaceLabels...),
|
||||
),
|
||||
ifaceReceivedBytesTotal: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_interface_received_bytes_total",
|
||||
Help: "Bytes received througth the interface.",
|
||||
}, append(labels, ifaceLabels...),
|
||||
),
|
||||
ifaceSendBytesTotal: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_interface_sent_bytes_total",
|
||||
Help: "Bytes sent through the interface.",
|
||||
}, append(labels, ifaceLabels...),
|
||||
),
|
||||
|
||||
peerInfo: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_info",
|
||||
Help: "Peer info.",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
peerIsConnected: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_up",
|
||||
Help: "Peer connection state (boolean: 1/0).",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
peerLastHandshakeSeconds: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_last_handshake_seconds",
|
||||
Help: "Seconds from the last handshake with the peer.",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
peerReceivedBytesTotal: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_received_bytes_total",
|
||||
Help: "Bytes received from the peer.",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
peerSendBytesTotal: promauto.With(reg).NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "wireguard_peer_sent_bytes_total",
|
||||
Help: "Bytes sent to the peer.",
|
||||
}, append(labels, peerLabels...),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts the metrics server
|
||||
func (m *MetricsServer) Run(ctx context.Context) {
|
||||
// Run the metrics server in a goroutine
|
||||
go func() {
|
||||
if err := m.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
logrus.Errorf("metrics service on %s exited: %v", m.Addr, err)
|
||||
}
|
||||
}()
|
||||
|
||||
logrus.Infof("started metrics service on %s", m.Addr)
|
||||
|
||||
// Wait for the context to be done
|
||||
<-ctx.Done()
|
||||
|
||||
// Create a context with timeout for the shutdown process
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Attempt to gracefully shutdown the metrics server
|
||||
if err := m.Shutdown(shutdownCtx); err != nil {
|
||||
logrus.Errorf("metrics service on %s shutdown failed: %v", m.Addr, err)
|
||||
} else {
|
||||
logrus.Infof("metrics service on %s shutdown gracefully", m.Addr)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateInterfaceMetrics updates the metrics for the given interface
|
||||
func (m *MetricsServer) UpdateInterfaceMetrics(status domain.InterfaceStatus) {
|
||||
labels := []string{string(status.InterfaceId)}
|
||||
m.ifaceInfo.WithLabelValues(labels...).Set(1)
|
||||
m.ifaceReceivedBytesTotal.WithLabelValues(labels...).Set(float64(status.BytesReceived))
|
||||
m.ifaceSendBytesTotal.WithLabelValues(labels...).Set(float64(status.BytesTransmitted))
|
||||
}
|
||||
|
||||
// UpdatePeerMetrics updates the metrics for the given peer
|
||||
func (m *MetricsServer) UpdatePeerMetrics(ctx context.Context, status domain.PeerStatus) {
|
||||
// Fetch peer data from the database
|
||||
peer, err := m.db.GetPeer(ctx, status.PeerId)
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to fetch peer data for labels %s: %v", status.PeerId, err)
|
||||
return
|
||||
}
|
||||
|
||||
labels := []string{
|
||||
string(peer.InterfaceIdentifier),
|
||||
string(peer.Interface.AddressStr()),
|
||||
string(status.PeerId),
|
||||
string(peer.DisplayName),
|
||||
}
|
||||
|
||||
m.peerInfo.WithLabelValues(labels...).Set(1)
|
||||
if status.LastHandshake != nil {
|
||||
m.peerLastHandshakeSeconds.WithLabelValues(labels...).Set(float64(status.LastHandshake.Unix()))
|
||||
}
|
||||
m.peerReceivedBytesTotal.WithLabelValues(labels...).Set(float64(status.BytesReceived))
|
||||
m.peerSendBytesTotal.WithLabelValues(labels...).Set(float64(status.BytesTransmitted))
|
||||
m.peerIsConnected.WithLabelValues(labels...).Set(internal.BoolToFloat64(status.IsConnected()))
|
||||
}
|
@ -95,8 +95,6 @@ func NewServer(cfg *config.Config, endpoints ...ApiEndpointSetupFunc) (*Server,
|
||||
}
|
||||
|
||||
func (s *Server) Run(ctx context.Context, listenAddress string) {
|
||||
logrus.Infof("starting web service on %s", listenAddress)
|
||||
|
||||
// Run web service
|
||||
srv := &http.Server{
|
||||
Addr: listenAddress,
|
||||
@ -116,6 +114,7 @@ func (s *Server) Run(ctx context.Context, listenAddress string) {
|
||||
cancelFn()
|
||||
}
|
||||
}()
|
||||
logrus.Infof("started web service on %s", listenAddress)
|
||||
|
||||
// Wait for the main context to end
|
||||
<-srvContext.Done()
|
||||
|
@ -2,6 +2,7 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
)
|
||||
|
||||
@ -27,6 +28,7 @@ type InterfaceAndPeerDatabaseRepo interface {
|
||||
type StatisticsDatabaseRepo interface {
|
||||
GetAllInterfaces(ctx context.Context) ([]domain.Interface, error)
|
||||
GetInterfacePeers(ctx context.Context, id domain.InterfaceIdentifier) ([]domain.Peer, error)
|
||||
GetPeer(ctx context.Context, id domain.PeerIdentifier) (*domain.Peer, error)
|
||||
|
||||
UpdatePeerStatus(ctx context.Context, id domain.PeerIdentifier, updateFunc func(in *domain.PeerStatus) (*domain.PeerStatus, error)) error
|
||||
UpdateInterfaceStatus(ctx context.Context, id domain.InterfaceIdentifier, updateFunc func(in *domain.InterfaceStatus) (*domain.InterfaceStatus, error)) error
|
||||
@ -48,3 +50,8 @@ type WgQuickController interface {
|
||||
SetDNS(id domain.InterfaceIdentifier, dnsStr, dnsSearchStr string) error
|
||||
UnsetDNS(id domain.InterfaceIdentifier) error
|
||||
}
|
||||
|
||||
type MetricsServer interface {
|
||||
UpdateInterfaceMetrics(status domain.InterfaceStatus)
|
||||
UpdatePeerMetrics(ctx context.Context, status domain.PeerStatus)
|
||||
}
|
||||
|
@ -2,12 +2,13 @@ package wireguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
"github.com/prometheus-community/pro-bing"
|
||||
"github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/h44z/wg-portal/internal/config"
|
||||
"github.com/h44z/wg-portal/internal/domain"
|
||||
probing "github.com/prometheus-community/pro-bing"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type StatisticsCollector struct {
|
||||
@ -18,14 +19,16 @@ type StatisticsCollector struct {
|
||||
|
||||
db StatisticsDatabaseRepo
|
||||
wg InterfaceController
|
||||
ms MetricsServer
|
||||
}
|
||||
|
||||
func NewStatisticsCollector(cfg *config.Config, db StatisticsDatabaseRepo, wg InterfaceController) (*StatisticsCollector, error) {
|
||||
func NewStatisticsCollector(cfg *config.Config, db StatisticsDatabaseRepo, wg InterfaceController, ms MetricsServer) (*StatisticsCollector, error) {
|
||||
return &StatisticsCollector{
|
||||
cfg: cfg,
|
||||
|
||||
db: db,
|
||||
wg: wg,
|
||||
ms: ms,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -70,11 +73,15 @@ func (c *StatisticsCollector) collectInterfaceData(ctx context.Context) {
|
||||
i.UpdatedAt = time.Now()
|
||||
i.BytesReceived = physicalInterface.BytesDownload
|
||||
i.BytesTransmitted = physicalInterface.BytesUpload
|
||||
|
||||
// Update prometheus metrics
|
||||
go c.ms.UpdateInterfaceMetrics(*i)
|
||||
return i, nil
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to update interface status for %s: %v", in.Identifier, err)
|
||||
}
|
||||
logrus.Tracef("updated interface status for %s", in.Identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -126,11 +133,15 @@ func (c *StatisticsCollector) collectPeerData(ctx context.Context) {
|
||||
p.Endpoint = peer.Endpoint
|
||||
p.LastHandshake = lastHandshake
|
||||
|
||||
// Update prometheus metrics
|
||||
go c.ms.UpdatePeerMetrics(ctx, *p)
|
||||
|
||||
return p, nil
|
||||
})
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to update interface status for %s: %v", in.Identifier, err)
|
||||
}
|
||||
logrus.Tracef("updated peer status for %s", peer.Identifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -234,7 +245,7 @@ func (c *StatisticsCollector) pingWorker(ctx context.Context) {
|
||||
}
|
||||
|
||||
func (c *StatisticsCollector) isPeerPingable(ctx context.Context, peer domain.Peer) bool {
|
||||
if c.cfg.Statistics.UsePingChecks == false {
|
||||
if !c.cfg.Statistics.UsePingChecks {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ type Config struct {
|
||||
CollectInterfaceData bool `yaml:"collect_interface_data"`
|
||||
CollectPeerData bool `yaml:"collect_peer_data"`
|
||||
CollectAuditData bool `yaml:"collect_audit_data"`
|
||||
ListeningAddress string `yaml:"listening_address"`
|
||||
} `yaml:"statistics"`
|
||||
|
||||
Mail MailConfig `yaml:"mail"`
|
||||
@ -117,10 +118,11 @@ func defaultConfig() *Config {
|
||||
cfg.Statistics.PingCheckWorkers = 10
|
||||
cfg.Statistics.PingUnprivileged = false
|
||||
cfg.Statistics.PingCheckInterval = 1 * time.Minute
|
||||
cfg.Statistics.DataCollectionInterval = 10 * time.Second
|
||||
cfg.Statistics.DataCollectionInterval = 1 * time.Minute
|
||||
cfg.Statistics.CollectInterfaceData = true
|
||||
cfg.Statistics.CollectPeerData = true
|
||||
cfg.Statistics.CollectAuditData = true
|
||||
cfg.Statistics.ListeningAddress = ":8787"
|
||||
|
||||
cfg.Mail = MailConfig{
|
||||
Host: "127.0.0.1",
|
||||
|
@ -126,3 +126,10 @@ func TruncateString(s string, max int) string {
|
||||
}
|
||||
return s[:max]
|
||||
}
|
||||
|
||||
func BoolToFloat64(b bool) float64 {
|
||||
if b {
|
||||
return 1.0
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user