From 070c3e1575ff29e8fe4488a7641282c6dafdce43 Mon Sep 17 00:00:00 2001 From: Tero Halla-aho Date: Tue, 16 Dec 2025 11:24:06 +0200 Subject: [PATCH] Add Loki logging stack --- PROGRESS.md | 1 + deploy/install-logging.sh | 56 +++++++++++++++++++++++++++++ deploy/update-logs-dns.sh | 22 ++++++++++++ docs/logging.md | 52 +++++++++++++++++++++++++++ k8s/logging/grafana-values.yaml | 45 +++++++++++++++++++++++ k8s/logging/loki-values.yaml | 61 ++++++++++++++++++++++++++++++++ k8s/logging/promtail-values.yaml | 25 +++++++++++++ k8s/namespaces.yaml | 5 +++ 8 files changed, 267 insertions(+) create mode 100644 deploy/install-logging.sh create mode 100644 deploy/update-logs-dns.sh create mode 100644 docs/logging.md create mode 100644 k8s/logging/grafana-values.yaml create mode 100644 k8s/logging/loki-values.yaml create mode 100644 k8s/logging/promtail-values.yaml diff --git a/PROGRESS.md b/PROGRESS.md index 7eec291..df5f692 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -66,6 +66,7 @@ - Price hint now stored in euros (schema field `priceHintPerNightEuros`); Prisma migration added to convert from cents, seeds and API/UI updated, and build now runs `prisma generate` automatically. - Listing creation amenities UI improved with toggle cards and EV button group. - Edit listing form now matches the create form styling, including amenity icon grid and price helpers. +- Centralized logging stack scaffolded (Loki + Promtail + Grafana) with Helm values and install script; Grafana ingress defaults to `logs.lomavuokraus.fi`. - Mermaid docs fixed: all sequence diagrams declare their participants and avoid “->” inside message text; the listing creation diagram message was rewritten to prevent parse errors. Use mermaid.live or browser console to debug future syntax issues (errors flag the offending line/column). - New amenities added: kitchen, dishwasher, washing machine, barbecue; API/UI/i18n updated and seeds randomized to populate missing prices/amenities. Prisma migration `20250210_more_amenities` applied to shared DB; registry pull secret added to k8s Deployment to avoid image pull errors in prod. - Added About and Pricing pages (FI/EN), moved highlights/runtime config to About, and linked footer navigation. diff --git a/deploy/install-logging.sh b/deploy/install-logging.sh new file mode 100644 index 0000000..8874270 --- /dev/null +++ b/deploy/install-logging.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/.." + +if [[ -f scripts/load-secrets.sh ]]; then + source scripts/load-secrets.sh +fi + +LOGGING_NAMESPACE="${LOGGING_NAMESPACE:-logging}" +LOGS_HOST="${LOGS_HOST:-logs.lomavuokraus.fi}" +GRAFANA_CLUSTER_ISSUER="${GRAFANA_CLUSTER_ISSUER:-letsencrypt-prod}" +: "${GRAFANA_ADMIN_PASSWORD:?Set GRAFANA_ADMIN_PASSWORD to provision Grafana.}" + +HELM_BIN="${HELM_BIN:-$(command -v helm || true)}" + +ensure_helm() { + if [[ -n "$HELM_BIN" && -x "$HELM_BIN" ]]; then + return + fi + echo "Helm not found, downloading to a temp dir..." + TMP_DIR="$(mktemp -d)" + curl -fsSL https://get.helm.sh/helm-v3.16.1-linux-amd64.tar.gz | tar -xz -C "$TMP_DIR" + HELM_BIN="$TMP_DIR/linux-amd64/helm" +} + +ensure_helm + +echo "Using helm at: $HELM_BIN" + +$HELM_BIN repo add grafana https://grafana.github.io/helm-charts +$HELM_BIN repo update + +export LOGS_HOST GRAFANA_CLUSTER_ISSUER GRAFANA_ADMIN_PASSWORD + +LOKI_TMP=$(mktemp) +PROMTAIL_TMP=$(mktemp) +GRAFANA_TMP=$(mktemp) + +cat k8s/logging/loki-values.yaml >"$LOKI_TMP" +cat k8s/logging/promtail-values.yaml >"$PROMTAIL_TMP" +envsubst < k8s/logging/grafana-values.yaml >"$GRAFANA_TMP" + +echo "Installing/Upgrading Loki..." +$HELM_BIN upgrade --install loki grafana/loki -n "$LOGGING_NAMESPACE" -f "$LOKI_TMP" --create-namespace + +echo "Installing/Upgrading Promtail..." +$HELM_BIN upgrade --install promtail grafana/promtail -n "$LOGGING_NAMESPACE" -f "$PROMTAIL_TMP" + +echo "Installing/Upgrading Grafana..." +$HELM_BIN upgrade --install grafana grafana/grafana -n "$LOGGING_NAMESPACE" -f "$GRAFANA_TMP" + +echo "Resources in $LOGGING_NAMESPACE:" +kubectl get pods,svc,ingress -n "$LOGGING_NAMESPACE" + +echo "Done. Grafana ingress host: https://${LOGS_HOST}" diff --git a/deploy/update-logs-dns.sh b/deploy/update-logs-dns.sh new file mode 100644 index 0000000..e2b3e74 --- /dev/null +++ b/deploy/update-logs-dns.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/.." + +AUTH_FILE="creds/joker_com_dyndns_creds.txt" +if [[ ! -f "$AUTH_FILE" ]]; then + echo "Joker DYNDNS credentials missing at $AUTH_FILE" >&2 + exit 1 +fi + +JOKER_AUTH="$(cat "$AUTH_FILE")" +TARGET_IP="${TARGET_IP:-157.180.66.64}" +LOGS_HOST="${LOGS_HOST:-logs.lomavuokraus.fi}" + +echo "Updating $LOGS_HOST -> $TARGET_IP" +resp="$(curl -sS -u "$JOKER_AUTH" "https://svc.joker.com/nic/update?hostname=${LOGS_HOST}&myip=${TARGET_IP}")" +echo "$resp" +if [[ "$resp" != good* && "$resp" != nochg* ]]; then + echo "DNS update failed for $LOGS_HOST (response: $resp)" >&2 + exit 1 +fi diff --git a/docs/logging.md b/docs/logging.md new file mode 100644 index 0000000..93db8ba --- /dev/null +++ b/docs/logging.md @@ -0,0 +1,52 @@ +# Centralized logging (Loki + Promtail + Grafana) + +We ship a lightweight logging stack into the cluster so API/UI logs are searchable. + +- **Loki** (single-binary) stores logs with 14d retention by default, on a PVC. +- **Promtail** DaemonSet tails container logs and ships them to Loki with `namespace`, `pod`, and `app` labels. +- **Grafana** provides the UI with a pre-wired Loki data source and TLS ingress. + +## Install / upgrade + +Prereqs: +- `kubectl`/`helm` access to the cluster (the script downloads Helm if missing). +- Environment: `GRAFANA_ADMIN_PASSWORD` (required), optional `LOGS_HOST` (default `logs.lomavuokraus.fi`), `GRAFANA_CLUSTER_ISSUER` (default `letsencrypt-prod`), `LOGGING_NAMESPACE` (default `logging`). + +Run: + +```bash +LOGS_HOST=logs.lomavuokraus.fi \ +GRAFANA_ADMIN_PASSWORD='change-me' \ +GRAFANA_CLUSTER_ISSUER=letsencrypt-prod \ +bash deploy/install-logging.sh +``` + +The script: +1. Ensures Helm is available. +2. Installs/updates Loki, Promtail, and Grafana in the logging namespace. +3. Creates a Grafana ingress with TLS via the chosen ClusterIssuer. + +## Access + +- Grafana: `https://` (admin user `admin`, password from `GRAFANA_ADMIN_PASSWORD`). +- Loki endpoint (internal): `http://loki.logging.svc.cluster.local:3100`. + +## Querying + +Example LogQL in Grafana Explore: + +``` +{namespace="lomavuokraus-test", app="lomavuokraus-web"} +``` + +Filter by pod: + +``` +{namespace="lomavuokraus-test", app="lomavuokraus-web", pod=~".*"} |= "ERROR" +``` + +## Tuning + +- Retention: `k8s/logging/loki-values.yaml` (`limits_config.retention_period`). +- PVC sizes: adjust `persistence.size` in `k8s/logging/loki-values.yaml` and `k8s/logging/grafana-values.yaml`. +- Ingress issuer/host: override via environment when running `deploy/install-logging.sh`. diff --git a/k8s/logging/grafana-values.yaml b/k8s/logging/grafana-values.yaml new file mode 100644 index 0000000..3794f86 --- /dev/null +++ b/k8s/logging/grafana-values.yaml @@ -0,0 +1,45 @@ +adminUser: admin +adminPassword: "${GRAFANA_ADMIN_PASSWORD}" + +initChownData: + enabled: false + +persistence: + enabled: true + size: 10Gi + storageClassName: "" + +service: + type: ClusterIP + +ingress: + enabled: true + ingressClassName: traefik + annotations: + cert-manager.io/cluster-issuer: "${GRAFANA_CLUSTER_ISSUER}" + hosts: + - "${LOGS_HOST}" + tls: + - hosts: + - "${LOGS_HOST}" + secretName: grafana-logs-tls + +datasources: + datasources.yaml: + apiVersion: 1 + datasources: + - name: Loki + type: loki + access: proxy + url: http://loki.logging.svc.cluster.local:3100 + isDefault: true + jsonData: + timeout: 60 + maxLines: 5000 + +grafana.ini: + server: + root_url: https://${LOGS_HOST} + analytics: + reporting_enabled: false + check_for_updates: false diff --git a/k8s/logging/loki-values.yaml b/k8s/logging/loki-values.yaml new file mode 100644 index 0000000..553d2db --- /dev/null +++ b/k8s/logging/loki-values.yaml @@ -0,0 +1,61 @@ +deploymentMode: SingleBinary + +singleBinary: + replicas: 1 + persistence: + enabled: true + size: 20Gi + storageClass: "" + +# Disable simple scalable targets to satisfy chart validation +write: + replicas: 0 +read: + replicas: 0 +backend: + replicas: 0 + +loki: + auth_enabled: false + commonConfig: + replication_factor: 1 + storage: + type: filesystem + filesystem: + chunks_directory: /var/loki/chunks + rules_directory: /var/loki/rules + limits_config: + retention_period: 0s + compactor: + retention_enabled: false + retention_delete_delay: 2h + retention_delete_worker_count: 1 + ruler: + storage: + type: local + local: + directory: /rules + schemaConfig: + configs: + - from: "2024-01-01" + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: loki_index_ + period: 24h + storage_config: + filesystem: + directory: /var/loki/chunks + +ingress: + enabled: false + +service: + type: ClusterIP + +chunksCache: + enabled: false + +resultsCache: + enabled: false diff --git a/k8s/logging/promtail-values.yaml b/k8s/logging/promtail-values.yaml new file mode 100644 index 0000000..f8acd45 --- /dev/null +++ b/k8s/logging/promtail-values.yaml @@ -0,0 +1,25 @@ +config: + lokiAddress: http://loki.logging.svc.cluster.local:3100/loki/api/v1/push + snippets: + extraRelabelConfigs: + # Skip Promtail's own logs to reduce noise + - source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name] + action: drop + regex: promtail + # Drop logs from the logging stack itself + - source_labels: [__meta_kubernetes_namespace] + action: drop + regex: logging + # Promote common labels for easier querying + - action: replace + replacement: $1 + source_labels: [__meta_kubernetes_namespace] + target_label: namespace + - action: replace + replacement: $1 + source_labels: [__meta_kubernetes_pod_name] + target_label: pod + - action: replace + replacement: $1 + source_labels: [__meta_kubernetes_pod_label_app_kubernetes_io_name] + target_label: app diff --git a/k8s/namespaces.yaml b/k8s/namespaces.yaml index f5cee12..f1f8969 100644 --- a/k8s/namespaces.yaml +++ b/k8s/namespaces.yaml @@ -12,3 +12,8 @@ apiVersion: v1 kind: Namespace metadata: name: lomavuokraus-test +--- +apiVersion: v1 +kind: Namespace +metadata: + name: logging