Move Prom metrics to a dedicated port + improve Helm chart (#599)
This commit is contained in:
@@ -31,7 +31,7 @@ RUN apk add --no-cache \
|
|||||||
gnupg \
|
gnupg \
|
||||||
xz
|
xz
|
||||||
|
|
||||||
EXPOSE 6157 2222 16157
|
EXPOSE 6157 6158 2222 16157
|
||||||
|
|
||||||
RUN git config --global --add safe.directory /opengist
|
RUN git config --global --add safe.directory /opengist
|
||||||
RUN make install
|
RUN make install
|
||||||
@@ -64,7 +64,7 @@ COPY --from=build --chown=opengist:opengist /opengist/config.yml /config.yml
|
|||||||
COPY --from=build --chown=opengist:opengist /opengist/opengist .
|
COPY --from=build --chown=opengist:opengist /opengist/opengist .
|
||||||
COPY --from=build --chown=opengist:opengist /opengist/docker ./docker
|
COPY --from=build --chown=opengist:opengist /opengist/docker ./docker
|
||||||
|
|
||||||
EXPOSE 6157 2222
|
EXPOSE 6157 6158 2222
|
||||||
VOLUME /opengist
|
VOLUME /opengist
|
||||||
HEALTHCHECK --interval=60s --timeout=30s --start-period=15s --retries=3 CMD curl -f http://localhost:6157/healthcheck || exit 1
|
HEALTHCHECK --interval=60s --timeout=30s --start-period=15s --retries=3 CMD curl -f http://localhost:6157/healthcheck || exit 1
|
||||||
ENTRYPOINT ["./docker/entrypoint.sh"]
|
ENTRYPOINT ["./docker/entrypoint.sh"]
|
||||||
|
|||||||
@@ -55,9 +55,15 @@ http.git-enabled: true
|
|||||||
# File permissions for Unix socket (octal format). Default: 0666
|
# File permissions for Unix socket (octal format). Default: 0666
|
||||||
unix-socket-permissions: 0666
|
unix-socket-permissions: 0666
|
||||||
|
|
||||||
# Enable or disable the metrics endpoint (either `true` or `false`). Default: false
|
# Enable or disable the Prometheus metrics server (either `true` or `false`). Default: false
|
||||||
metrics.enabled: false
|
metrics.enabled: false
|
||||||
|
|
||||||
|
# The host on which the metrics server should bind. Default: 0.0.0.0
|
||||||
|
metrics.host: 0.0.0.0
|
||||||
|
|
||||||
|
# The port on which the metrics server should listen. Default: 6158
|
||||||
|
metrics.port: 6158
|
||||||
|
|
||||||
# SSH built-in server configuration
|
# SSH built-in server configuration
|
||||||
# Note: it is not using the SSH daemon from your machine (yet)
|
# Note: it is not using the SSH daemon from your machine (yet)
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ aside: false
|
|||||||
| http.port | OG_HTTP_PORT | `6157` | The port on which the HTTP server should listen. |
|
| http.port | OG_HTTP_PORT | `6157` | The port on which the HTTP server should listen. |
|
||||||
| http.git-enabled | OG_HTTP_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via HTTP. (`true` or `false`) |
|
| http.git-enabled | OG_HTTP_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via HTTP. (`true` or `false`) |
|
||||||
| unix-socket-permissions | OG_UNIX_SOCKET_PERMISSIONS | `0666` | File permissions for Unix socket (octal format). |
|
| unix-socket-permissions | OG_UNIX_SOCKET_PERMISSIONS | `0666` | File permissions for Unix socket (octal format). |
|
||||||
| metrics.enabled | OG_METRICS_ENABLED | `false` | Enable or disable Prometheus metrics endpoint at `/metrics` (`true` or `false`) |
|
| metrics.enabled | OG_METRICS_ENABLED | `false` | Enable or disable Prometheus metrics server (`true` or `false`) |
|
||||||
|
| metrics.host | OG_METRICS_HOST | `0.0.0.0` | The host on which the metrics server should bind. |
|
||||||
|
| metrics.port | OG_METRICS_PORT | `6158` | The port on which the metrics server should listen. |
|
||||||
| ssh.git-enabled | OG_SSH_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via SSH. (`true` or `false`) |
|
| ssh.git-enabled | OG_SSH_GIT_ENABLED | `true` | Enable or disable git operations (clone, pull, push) via SSH. (`true` or `false`) |
|
||||||
| ssh.host | OG_SSH_HOST | `0.0.0.0` | The host on which the SSH server should bind. |
|
| ssh.host | OG_SSH_HOST | `0.0.0.0` | The host on which the SSH server should bind. |
|
||||||
| ssh.port | OG_SSH_PORT | `2222` | The port on which the SSH server should listen. |
|
| ssh.port | OG_SSH_PORT | `2222` | The port on which the SSH server should listen. |
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ Opengist offers built-in support for Prometheus metrics to help you monitor the
|
|||||||
|
|
||||||
## Enabling metrics
|
## Enabling metrics
|
||||||
|
|
||||||
By default, the metrics endpoint is disabled for security and performance reasons. To enable it, update your configuration as stated in the [configuration cheat sheet](cheat-sheet.md):
|
By default, the metrics server is disabled for security and performance reasons. To enable it, update your configuration as stated in the [configuration cheat sheet](cheat-sheet.md):
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
metrics.enabled = true
|
metrics.enabled: true
|
||||||
```
|
```
|
||||||
|
|
||||||
Alternatively, you can use the environment variable:
|
Alternatively, you can use the environment variable:
|
||||||
@@ -16,7 +16,25 @@ Alternatively, you can use the environment variable:
|
|||||||
OG_METRICS_ENABLED=true
|
OG_METRICS_ENABLED=true
|
||||||
```
|
```
|
||||||
|
|
||||||
Once enabled, metrics are available at the /metrics endpoint.
|
Once enabled, metrics are available on a separate server at `http://0.0.0.0:6158/metrics` by default.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The metrics server runs on a separate port from the main application. By default, it binds to `0.0.0.0` (all interfaces) on port `6158`.
|
||||||
|
|
||||||
|
| Config Key | Environment Variable | Default | Description |
|
||||||
|
|----------------|---------------------|-------------|------------------------------------------------|
|
||||||
|
| metrics.enabled | OG_METRICS_ENABLED | `false` | Enable or disable the metrics server |
|
||||||
|
| metrics.host | OG_METRICS_HOST | `0.0.0.0` | The host on which the metrics server binds |
|
||||||
|
| metrics.port | OG_METRICS_PORT | `6158` | The port on which the metrics server listens |
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
metrics.enabled: true
|
||||||
|
metrics.host: 0.0.0.0
|
||||||
|
metrics.port: 6158
|
||||||
|
```
|
||||||
|
|
||||||
## Available metrics
|
## Available metrics
|
||||||
|
|
||||||
@@ -36,14 +54,6 @@ These standard metrics follow the Prometheus naming convention and include label
|
|||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
The metrics endpoint exposes information about your Opengist instance that might be sensitive in some environments. Consider using a reverse proxy with authentication for the `/metrics` endpoint if your Opengist instance is publicly accessible.
|
The metrics server binds to `0.0.0.0` by default, making it accessible on all network interfaces. This default works well for containerized deployments (Docker, Kubernetes) where network isolation is handled at the infrastructure level.
|
||||||
|
|
||||||
Example with Nginx:
|
For bare-metal or VM deployments where the metrics port may be exposed, consider restricting to localhost by setting `metrics.host: 127.0.0.1` to only allow local access.
|
||||||
|
|
||||||
```shell
|
|
||||||
location /metrics {
|
|
||||||
auth_basic "Metrics";
|
|
||||||
auth_basic_user_file /etc/nginx/.htpasswd;
|
|
||||||
proxy_pass http://localhost:6157/metrics;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ Opengist Helm chart for Kubernetes.
|
|||||||
|
|
||||||
* [Install](#install)
|
* [Install](#install)
|
||||||
* [Configuration](#configuration)
|
* [Configuration](#configuration)
|
||||||
|
* [Metrics & Monitoring](#metrics--monitoring)
|
||||||
* [Dependencies](#dependencies)
|
* [Dependencies](#dependencies)
|
||||||
* [Meilisearch Indexer](#meilisearch-indexer)
|
* [Meilisearch Indexer](#meilisearch-indexer)
|
||||||
* [PostgreSQL Database](#postgresql-database)
|
* [PostgreSQL Database](#postgresql-database)
|
||||||
@@ -47,6 +48,76 @@ If defined, this existing secret will be used instead of creating a new one.
|
|||||||
configExistingSecret: <name of the secret>
|
configExistingSecret: <name of the secret>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Metrics & Monitoring
|
||||||
|
|
||||||
|
Opengist exposes Prometheus metrics on a separate port (default: `6158`). The metrics server runs independently from the main HTTP server for security.
|
||||||
|
|
||||||
|
### Enabling Metrics
|
||||||
|
|
||||||
|
To enable metrics, set `metrics.enabled: true` in your Opengist config:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
config:
|
||||||
|
metrics.enabled: true
|
||||||
|
```
|
||||||
|
|
||||||
|
This will:
|
||||||
|
1. Start a metrics server on port 6158 inside the container
|
||||||
|
2. Create a Kubernetes Service exposing the metrics ports
|
||||||
|
|
||||||
|
### Available Metrics
|
||||||
|
|
||||||
|
| Metric Name | Type | Description |
|
||||||
|
|-------------|------|-------------|
|
||||||
|
| `opengist_users_total` | Gauge | Total number of registered users |
|
||||||
|
| `opengist_gists_total` | Gauge | Total number of gists |
|
||||||
|
| `opengist_ssh_keys_total` | Gauge | Total number of SSH keys |
|
||||||
|
| `opengist_request_duration_seconds_*` | Histogram | HTTP request duration metrics |
|
||||||
|
|
||||||
|
### ServiceMonitor for Prometheus Operator
|
||||||
|
|
||||||
|
If you're using [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator), you can enable automatic service discovery with a ServiceMonitor:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
config:
|
||||||
|
metrics.enabled: true
|
||||||
|
|
||||||
|
service:
|
||||||
|
metrics:
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: true
|
||||||
|
labels:
|
||||||
|
release: prometheus # match your Prometheus serviceMonitorSelector
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Prometheus Configuration
|
||||||
|
|
||||||
|
If you're not using Prometheus Operator, you can configure Prometheus to scrape the metrics endpoint directly:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'opengist'
|
||||||
|
static_configs:
|
||||||
|
- targets: ['opengist-metrics:6158']
|
||||||
|
metrics_path: /metrics
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use Kubernetes service discovery:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: 'opengist'
|
||||||
|
kubernetes_sd_configs:
|
||||||
|
- role: service
|
||||||
|
relabel_configs:
|
||||||
|
- source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_component]
|
||||||
|
regex: metrics
|
||||||
|
action: keep
|
||||||
|
- source_labels: [__meta_kubernetes_service_label_app_kubernetes_io_name]
|
||||||
|
regex: opengist
|
||||||
|
action: keep
|
||||||
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
### Meilisearch Indexer
|
### Meilisearch Indexer
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ spec:
|
|||||||
- name: http
|
- name: http
|
||||||
containerPort: {{ .Values.service.http.port }}
|
containerPort: {{ .Values.service.http.port }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
{{- if index .Values.config "metrics.enabled" }}
|
||||||
|
- name: metrics
|
||||||
|
containerPort: {{ .Values.service.metrics.port }}
|
||||||
|
protocol: TCP
|
||||||
|
{{- end }}
|
||||||
{{- if .Values.livenessProbe.enabled }}
|
{{- if .Values.livenessProbe.enabled }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
{{- toYaml (omit .Values.livenessProbe "enabled") | nindent 12 }}
|
{{- toYaml (omit .Values.livenessProbe "enabled") | nindent 12 }}
|
||||||
|
|||||||
41
helm/opengist/templates/servicemonitor.yaml
Normal file
41
helm/opengist/templates/servicemonitor.yaml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
{{- if and (index .Values.config "metrics.enabled") .Values.service.metrics.serviceMonitor.enabled }}
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: {{ include "opengist.fullname" . }}
|
||||||
|
namespace: {{ .Values.namespace | default .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "opengist.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.service.metrics.serviceMonitor.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.service.metrics.serviceMonitor.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
endpoints:
|
||||||
|
- port: metrics
|
||||||
|
{{- with .Values.service.metrics.serviceMonitor.interval }}
|
||||||
|
interval: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.service.metrics.serviceMonitor.scrapeTimeout }}
|
||||||
|
scrapeTimeout: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
path: /metrics
|
||||||
|
{{- with .Values.service.metrics.serviceMonitor.relabelings }}
|
||||||
|
relabelings:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.service.metrics.serviceMonitor.metricRelabelings }}
|
||||||
|
metricRelabelings:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
namespaceSelector:
|
||||||
|
matchNames:
|
||||||
|
- {{ .Values.namespace | default .Release.Namespace }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "opengist.selectorLabels" . | nindent 6 }}
|
||||||
|
app.kubernetes.io/component: metrics
|
||||||
|
{{- end }}
|
||||||
@@ -140,6 +140,11 @@ spec:
|
|||||||
containerPort: {{ .Values.service.ssh.port }}
|
containerPort: {{ .Values.service.ssh.port }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if index .Values.config "metrics.enabled" }}
|
||||||
|
- name: metrics
|
||||||
|
containerPort: {{ .Values.service.metrics.port }}
|
||||||
|
protocol: TCP
|
||||||
|
{{- end }}
|
||||||
{{- if .Values.livenessProbe.enabled }}
|
{{- if .Values.livenessProbe.enabled }}
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
{{- toYaml (omit .Values.livenessProbe "enabled") | nindent 12 }}
|
{{- toYaml (omit .Values.livenessProbe "enabled") | nindent 12 }}
|
||||||
|
|||||||
32
helm/opengist/templates/svc-metrics.yaml
Normal file
32
helm/opengist/templates/svc-metrics.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{{- if index .Values.config "metrics.enabled" }}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "opengist.fullname" . }}-metrics
|
||||||
|
namespace: {{ .Values.namespace | default .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "opengist.labels" . | nindent 4 }}
|
||||||
|
app.kubernetes.io/component: metrics
|
||||||
|
{{- with .Values.service.metrics.labels }}
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.service.metrics.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.metrics.type }}
|
||||||
|
{{- if .Values.service.metrics.clusterIP }}
|
||||||
|
clusterIP: {{ .Values.service.metrics.clusterIP }}
|
||||||
|
{{- end }}
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.metrics.port }}
|
||||||
|
targetPort: metrics
|
||||||
|
protocol: TCP
|
||||||
|
name: metrics
|
||||||
|
{{- if and (eq .Values.service.metrics.type "NodePort") .Values.service.metrics.nodePort }}
|
||||||
|
nodePort: {{ .Values.service.metrics.nodePort }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
{{- include "opengist.selectorLabels" . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
@@ -8,6 +8,7 @@ namespace: ""
|
|||||||
config:
|
config:
|
||||||
log-level: "warn"
|
log-level: "warn"
|
||||||
log-output: "stdout"
|
log-output: "stdout"
|
||||||
|
metrics.enabled: false
|
||||||
|
|
||||||
## If defined, the existing secret will be used instead of creating a new one.
|
## If defined, the existing secret will be used instead of creating a new one.
|
||||||
## The secret must contain a key named `config.yml` with the YAML configuration.
|
## The secret must contain a key named `config.yml` with the YAML configuration.
|
||||||
@@ -101,6 +102,26 @@ service:
|
|||||||
loadBalancerSourceRanges: []
|
loadBalancerSourceRanges: []
|
||||||
externalTrafficPolicy:
|
externalTrafficPolicy:
|
||||||
|
|
||||||
|
# A metrics K8S service on port 6158 is created when the Opengist config metrics.enabled: true
|
||||||
|
metrics:
|
||||||
|
type: ClusterIP
|
||||||
|
clusterIP:
|
||||||
|
port: 6158
|
||||||
|
nodePort:
|
||||||
|
labels: {}
|
||||||
|
annotations: {}
|
||||||
|
|
||||||
|
# A service monitor can be used to work with your Prometheus setup.
|
||||||
|
serviceMonitor:
|
||||||
|
enabled: true
|
||||||
|
labels: {}
|
||||||
|
# release: kube-prom-stack
|
||||||
|
interval:
|
||||||
|
scrapeTimeout:
|
||||||
|
annotations: {}
|
||||||
|
relabelings: []
|
||||||
|
metricRelabelings: []
|
||||||
|
|
||||||
## HTTP Ingress for Opengist
|
## HTTP Ingress for Opengist
|
||||||
## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/
|
## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/
|
||||||
ingress:
|
ingress:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
"github.com/thomiceli/opengist/internal/index"
|
"github.com/thomiceli/opengist/internal/index"
|
||||||
"github.com/thomiceli/opengist/internal/ssh"
|
"github.com/thomiceli/opengist/internal/ssh"
|
||||||
|
"github.com/thomiceli/opengist/internal/web/handlers/metrics"
|
||||||
"github.com/thomiceli/opengist/internal/web/server"
|
"github.com/thomiceli/opengist/internal/web/server"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"os"
|
"os"
|
||||||
@@ -36,12 +37,18 @@ var CmdStart = cli.Command{
|
|||||||
|
|
||||||
Initialize(ctx)
|
Initialize(ctx)
|
||||||
|
|
||||||
server := server.NewServer(os.Getenv("OG_DEV") == "1", path.Join(config.GetHomeDir(), "sessions"), false)
|
httpServer := server.NewServer(os.Getenv("OG_DEV") == "1", path.Join(config.GetHomeDir(), "sessions"), false)
|
||||||
go server.Start()
|
go httpServer.Start()
|
||||||
go ssh.Start()
|
go ssh.Start()
|
||||||
|
|
||||||
|
var metricsServer *metrics.Server
|
||||||
|
if config.C.MetricsEnabled {
|
||||||
|
metricsServer = metrics.NewServer()
|
||||||
|
go metricsServer.Start()
|
||||||
|
}
|
||||||
|
|
||||||
<-stopCtx.Done()
|
<-stopCtx.Done()
|
||||||
shutdown(server)
|
shutdown(httpServer, metricsServer)
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -131,7 +138,7 @@ func Initialize(ctx *cli.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func shutdown(server *server.Server) {
|
func shutdown(httpServer *server.Server, metricsServer *metrics.Server) {
|
||||||
log.Info().Msg("Shutting down database...")
|
log.Info().Msg("Shutting down database...")
|
||||||
if err := db.Close(); err != nil {
|
if err := db.Close(); err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to close database")
|
log.Error().Err(err).Msg("Failed to close database")
|
||||||
@@ -142,7 +149,11 @@ func shutdown(server *server.Server) {
|
|||||||
index.Close()
|
index.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
server.Stop()
|
httpServer.Stop()
|
||||||
|
|
||||||
|
if metricsServer != nil {
|
||||||
|
metricsServer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
log.Info().Msg("Shutdown complete")
|
log.Info().Msg("Shutdown complete")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,9 @@ type config struct {
|
|||||||
OIDCGroupClaimName string `yaml:"oidc.group-claim-name" env:"OG_OIDC_GROUP_CLAIM_NAME"`
|
OIDCGroupClaimName string `yaml:"oidc.group-claim-name" env:"OG_OIDC_GROUP_CLAIM_NAME"`
|
||||||
OIDCAdminGroup string `yaml:"oidc.admin-group" env:"OG_OIDC_ADMIN_GROUP"`
|
OIDCAdminGroup string `yaml:"oidc.admin-group" env:"OG_OIDC_ADMIN_GROUP"`
|
||||||
|
|
||||||
MetricsEnabled bool `yaml:"metrics.enabled" env:"OG_METRICS_ENABLED"`
|
MetricsEnabled bool `yaml:"metrics.enabled" env:"OG_METRICS_ENABLED"`
|
||||||
|
MetricsHost string `yaml:"metrics.host" env:"OG_METRICS_HOST"`
|
||||||
|
MetricsPort string `yaml:"metrics.port" env:"OG_METRICS_PORT"`
|
||||||
|
|
||||||
LDAPUrl string `yaml:"ldap.url" env:"OG_LDAP_URL"`
|
LDAPUrl string `yaml:"ldap.url" env:"OG_LDAP_URL"`
|
||||||
LDAPBindDn string `yaml:"ldap.bind-dn" env:"OG_LDAP_BIND_DN"`
|
LDAPBindDn string `yaml:"ldap.bind-dn" env:"OG_LDAP_BIND_DN"`
|
||||||
@@ -128,6 +130,8 @@ func configWithDefaults() (*config, error) {
|
|||||||
c.GiteaName = "Gitea"
|
c.GiteaName = "Gitea"
|
||||||
|
|
||||||
c.MetricsEnabled = false
|
c.MetricsEnabled = false
|
||||||
|
c.MetricsHost = "0.0.0.0"
|
||||||
|
c.MetricsPort = "6158"
|
||||||
|
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/labstack/echo-contrib/echoprometheus"
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/web/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Using promauto to automatically register metrics with the default registry
|
|
||||||
countUsersGauge prometheus.Gauge
|
countUsersGauge prometheus.Gauge
|
||||||
countGistsGauge prometheus.Gauge
|
countGistsGauge prometheus.Gauge
|
||||||
countSSHKeysGauge prometheus.Gauge
|
countSSHKeysGauge prometheus.Gauge
|
||||||
@@ -18,84 +14,52 @@ var (
|
|||||||
metricsInitialized bool = false
|
metricsInitialized bool = false
|
||||||
)
|
)
|
||||||
|
|
||||||
// initMetrics initializes metrics if they're not already initialized
|
|
||||||
func initMetrics() {
|
func initMetrics() {
|
||||||
if metricsInitialized {
|
if metricsInitialized {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only initialize metrics if they're enabled
|
countUsersGauge = promauto.NewGauge(
|
||||||
if config.C.MetricsEnabled {
|
prometheus.GaugeOpts{
|
||||||
countUsersGauge = promauto.NewGauge(
|
Name: "opengist_users_total",
|
||||||
prometheus.GaugeOpts{
|
Help: "Total number of users",
|
||||||
Name: "opengist_users_total",
|
},
|
||||||
Help: "Total number of users",
|
)
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
countGistsGauge = promauto.NewGauge(
|
countGistsGauge = promauto.NewGauge(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "opengist_gists_total",
|
Name: "opengist_gists_total",
|
||||||
Help: "Total number of gists",
|
Help: "Total number of gists",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
countSSHKeysGauge = promauto.NewGauge(
|
countSSHKeysGauge = promauto.NewGauge(
|
||||||
prometheus.GaugeOpts{
|
prometheus.GaugeOpts{
|
||||||
Name: "opengist_ssh_keys_total",
|
Name: "opengist_ssh_keys_total",
|
||||||
Help: "Total number of SSH keys",
|
Help: "Total number of SSH keys",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
metricsInitialized = true
|
metricsInitialized = true
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateMetrics refreshes all metric values from the database
|
|
||||||
func updateMetrics() {
|
func updateMetrics() {
|
||||||
// Only update metrics if they're enabled
|
if !metricsInitialized {
|
||||||
if !config.C.MetricsEnabled || !metricsInitialized {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update users count
|
|
||||||
countUsers, err := db.CountAll(&db.User{})
|
countUsers, err := db.CountAll(&db.User{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
countUsersGauge.Set(float64(countUsers))
|
countUsersGauge.Set(float64(countUsers))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update gists count
|
|
||||||
countGists, err := db.CountAll(&db.Gist{})
|
countGists, err := db.CountAll(&db.Gist{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
countGistsGauge.Set(float64(countGists))
|
countGistsGauge.Set(float64(countGists))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update SSH keys count
|
|
||||||
countKeys, err := db.CountAll(&db.SSHKey{})
|
countKeys, err := db.CountAll(&db.SSHKey{})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
countSSHKeysGauge.Set(float64(countKeys))
|
countSSHKeysGauge.Set(float64(countKeys))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Metrics handles prometheus metrics endpoint requests.
|
|
||||||
func Metrics(ctx *context.Context) error {
|
|
||||||
// If metrics are disabled, return 404
|
|
||||||
if !config.C.MetricsEnabled {
|
|
||||||
return ctx.NotFound("Metrics endpoint is disabled")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize metrics if not already done
|
|
||||||
initMetrics()
|
|
||||||
|
|
||||||
// Update metrics
|
|
||||||
updateMetrics()
|
|
||||||
|
|
||||||
// Get the Echo context
|
|
||||||
echoCtx := ctx.Context
|
|
||||||
|
|
||||||
// Use the Prometheus metrics handler
|
|
||||||
handler := echoprometheus.NewHandler()
|
|
||||||
|
|
||||||
// Call the handler
|
|
||||||
return handler(echoCtx)
|
|
||||||
}
|
|
||||||
|
|||||||
50
internal/web/handlers/metrics/server.go
Normal file
50
internal/web/handlers/metrics/server.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo-contrib/echoprometheus"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
echo *echo.Echo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer() *Server {
|
||||||
|
e := echo.New()
|
||||||
|
e.HideBanner = true
|
||||||
|
e.HidePort = true
|
||||||
|
|
||||||
|
s := &Server{echo: e}
|
||||||
|
|
||||||
|
initMetrics()
|
||||||
|
|
||||||
|
e.GET("/metrics", func(ctx echo.Context) error {
|
||||||
|
updateMetrics()
|
||||||
|
return echoprometheus.NewHandler()(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Start() {
|
||||||
|
addr := config.C.MetricsHost + ":" + config.C.MetricsPort
|
||||||
|
log.Info().Msg("Starting metrics server on http://" + addr)
|
||||||
|
if err := s.echo.Start(addr); err != nil && err != http.ErrServerClosed {
|
||||||
|
log.Error().Err(err).Msg("Failed to start metrics server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() {
|
||||||
|
log.Info().Msg("Stopping metrics server...")
|
||||||
|
if err := s.echo.Close(); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to stop metrics server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
s.echo.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
@@ -37,8 +37,7 @@ func (s *Server) registerMiddlewares() {
|
|||||||
s.echo.Use(Middleware(dataInit).toEcho())
|
s.echo.Use(Middleware(dataInit).toEcho())
|
||||||
s.echo.Use(Middleware(locale).toEcho())
|
s.echo.Use(Middleware(locale).toEcho())
|
||||||
if config.C.MetricsEnabled {
|
if config.C.MetricsEnabled {
|
||||||
p := echoprometheus.NewMiddleware("opengist")
|
s.echo.Use(echoprometheus.NewMiddleware("opengist"))
|
||||||
s.echo.Use(p)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.echo.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{
|
s.echo.Pre(middleware.MethodOverrideWithConfig(middleware.MethodOverrideConfig{
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/thomiceli/opengist/internal/web/handlers/gist"
|
"github.com/thomiceli/opengist/internal/web/handlers/gist"
|
||||||
"github.com/thomiceli/opengist/internal/web/handlers/git"
|
"github.com/thomiceli/opengist/internal/web/handlers/git"
|
||||||
"github.com/thomiceli/opengist/internal/web/handlers/health"
|
"github.com/thomiceli/opengist/internal/web/handlers/health"
|
||||||
"github.com/thomiceli/opengist/internal/web/handlers/metrics"
|
|
||||||
"github.com/thomiceli/opengist/internal/web/handlers/settings"
|
"github.com/thomiceli/opengist/internal/web/handlers/settings"
|
||||||
"github.com/thomiceli/opengist/public"
|
"github.com/thomiceli/opengist/public"
|
||||||
)
|
)
|
||||||
@@ -34,10 +33,6 @@ func (s *Server) registerRoutes() {
|
|||||||
|
|
||||||
r.GET("/healthcheck", health.Healthcheck)
|
r.GET("/healthcheck", health.Healthcheck)
|
||||||
|
|
||||||
if config.C.MetricsEnabled {
|
|
||||||
r.GET("/metrics", metrics.Metrics)
|
|
||||||
}
|
|
||||||
|
|
||||||
r.GET("/register", auth.Register)
|
r.GET("/register", auth.Register)
|
||||||
r.POST("/register", auth.ProcessRegister)
|
r.POST("/register", auth.ProcessRegister)
|
||||||
r.GET("/login", auth.Login)
|
r.GET("/login", auth.Login)
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http/httptest"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -41,22 +41,12 @@ var (
|
|||||||
// - Total number of SSH keys
|
// - Total number of SSH keys
|
||||||
//
|
//
|
||||||
// The test follows these steps:
|
// The test follows these steps:
|
||||||
// 1. Enables metrics via environment variable
|
// 1. Sets up test environment
|
||||||
// 2. Sets up test environment
|
// 2. Registers and logs in an admin user
|
||||||
// 3. Registers and logs in an admin user
|
// 3. Creates a gist and adds an SSH key
|
||||||
// 4. Creates a gist and adds an SSH key
|
// 4. Creates a metrics server and queries the /metrics endpoint
|
||||||
// 5. Queries the metrics endpoint
|
// 5. Verifies the reported metrics match expected values
|
||||||
// 6. Verifies the reported metrics match expected values
|
|
||||||
//
|
|
||||||
// Environment variables:
|
|
||||||
// - OG_METRICS_ENABLED: Set to "true" for this test
|
|
||||||
func TestMetrics(t *testing.T) {
|
func TestMetrics(t *testing.T) {
|
||||||
originalValue := os.Getenv("OG_METRICS_ENABLED")
|
|
||||||
|
|
||||||
os.Setenv("OG_METRICS_ENABLED", "true")
|
|
||||||
|
|
||||||
defer os.Setenv("OG_METRICS_ENABLED", originalValue)
|
|
||||||
|
|
||||||
s := Setup(t)
|
s := Setup(t)
|
||||||
defer Teardown(t, s)
|
defer Teardown(t, s)
|
||||||
|
|
||||||
@@ -72,12 +62,16 @@ func TestMetrics(t *testing.T) {
|
|||||||
err = s.Request("POST", "/settings/ssh-keys", SSHKey, 302)
|
err = s.Request("POST", "/settings/ssh-keys", SSHKey, 302)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
var metricsRes http.Response
|
// Create a metrics server and query it
|
||||||
err = s.Request("GET", "/metrics", nil, 200, &metricsRes)
|
metricsServer := NewTestMetricsServer()
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
body, err := io.ReadAll(metricsRes.Body)
|
req := httptest.NewRequest("GET", "/metrics", nil)
|
||||||
defer metricsRes.Body.Close()
|
w := httptest.NewRecorder()
|
||||||
|
metricsServer.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
require.Equal(t, 200, w.Code)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(w.Body)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
lines := strings.Split(string(body), "\n")
|
lines := strings.Split(string(body), "\n")
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/thomiceli/opengist/internal/config"
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
"github.com/thomiceli/opengist/internal/db"
|
"github.com/thomiceli/opengist/internal/db"
|
||||||
"github.com/thomiceli/opengist/internal/git"
|
"github.com/thomiceli/opengist/internal/git"
|
||||||
|
"github.com/thomiceli/opengist/internal/web/handlers/metrics"
|
||||||
"github.com/thomiceli/opengist/internal/web/server"
|
"github.com/thomiceli/opengist/internal/web/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -240,3 +241,7 @@ type invitationAdmin struct {
|
|||||||
nbMax string `form:"nbMax"`
|
nbMax string `form:"nbMax"`
|
||||||
expiredAtUnix string `form:"expiredAtUnix"`
|
expiredAtUnix string `form:"expiredAtUnix"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewTestMetricsServer() *metrics.Server {
|
||||||
|
return metrics.NewServer()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user