feat(prd): move Loki to IONOS S3 and restore a highly-available distributed topology - #2814
Open
groundnuty wants to merge 2 commits into
Open
feat(prd): move Loki to IONOS S3 and restore a highly-available distributed topology#2814groundnuty wants to merge 2 commits into
groundnuty wants to merge 2 commits into
Conversation
…ibuted topology
Loki persisted ~470 days of logs but only ~2 hours were queryable. The
`filesystem` object store needs one filesystem visible to every component; the
ingester and querier each had their own ReadWriteOnce volume, so the querier
searched an empty 200Gi disk while 127 GiB of chunks sat on the ingester's.
Queries returned HTTP 200 with zero chunks, which Grafana renders as "No data".
A shared volume is not possible here: the IONOS CSI driver refuses to mount one
block device twice ("Resource busy"), even read-only, and no RWX storage class
exists. Object storage removes the constraint - every component reads the same
bucket - so the distributed topology is finally correct rather than broken.
Storage:
- object_store / shared_store: filesystem -> s3 (bucket loki-prd, eu-central-3)
- schema (v11), store (boltdb-shipper) and index period are UNCHANGED, which is
what lets the migrated historical chunks resolve
- credentials via SealedSecret + `-config.expand-env=true`; never in git
High availability:
- replication_factor 1 -> 2, so a single ingester restart no longer stops
ingestion cluster-wide (observed twice: 210 push-failures on 2026-07-13)
- ingester/distributor/querier/query-frontend/gateway now run 2 replicas
- PodDisruptionBudgets become meaningful now that replicas > 1 (see #2497)
Also:
- query-frontend was capped at 64Mi and OOMKilled every ~8 min (23 restarts),
which users experienced as intermittent Loki errors; given real resources
- memcached (index-queries, chunks, frontend) enabled - without caching every
query refetches from S3 and browsing months of logs is unusably slow
- serviceMonitor enabled; Loki previously exported no metrics at all, so
failures of the logging stack itself were invisible
- ingester PVC 200Gi -> 10Gi (WAL only; chunks live in S3), querier PVC removed
The existing 1,892,200 objects / 122.28 GiB covering 2025-04-18 -> 2026-07-31
were migrated to loki-prd and verified: a Loki reading only from S3 answered
queries at 1/7/30/90/180/300/400 days, matching pre-migration results.
Retention is deliberately NOT enabled here and is tracked separately.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gs dashboard Completes the logging work: without these, Loki is highly available but still does not see all pods, and developers have no usable way to read what it holds. promtail: tolerate the ingress-node taint - The DaemonSet ran on 4 of 5 nodes because it did not tolerate `ingress-node=true:NoSchedule`, so nothing scheduled on ingress-pool-* was collected at all - including the ingress-nginx controller's HTTP access logs, which are exactly what is needed to debug 4xx/5xx coming from a component. - Setting `tolerations` replaces the chart defaults, so the master and control-plane entries are carried over deliberately. - Verified with `helm template`: all three tolerations reach the DaemonSet. Grafana: "Application Logs" dashboard - Namespace / Pod / Level / free-text Search variables, so a developer can go from "my component is broken" to its logs without writing LogQL. - Log volume by pod and errors-over-time, plus line/error/pod-count stats. - Time shortcuts out to 1y to make the retained history discoverable. - Deployed via the existing ionos_prd/grafana-dashboards source of the kube-prometheus-stack Application; no Application change needed. - Datasource is referenced through a `datasource` variable because the Loki datasource has no fixed UID in this cluster. Every dashboard query and both label_values() variable queries were executed against a Loki reading from the migrated S3 bucket and returned HTTP 200 with data. Dashboard confirmed provisioned by Grafana (uid dome-application-logs). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Moves the production Loki deployment from local filesystem-backed storage to IONOS S3 to restore full log queryability, while re-establishing a highly-available distributed topology and improving operability (caching, metrics, dashboards).
Changes:
- Switch Loki
object_store/shared_storeto S3 (IONOS object storage), reduce local persistence to WAL-only, and scale core components to 2 replicas with meaningful disruption settings. - Enable Loki memcached caches and ServiceMonitor scraping to improve query performance and observability of the logging stack.
- Ensure complete log collection (Promtail tolerations) and add a new Grafana “Application Logs” dashboard.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| ionos_prd/promtail/values.yaml | Adds tolerations so Promtail schedules on ingress-tainted nodes and collects previously-missed logs. |
| ionos_prd/loki-distributed/values.yaml | Reconfigures Loki for S3-backed storage, HA replicas, caching, metrics, and updated persistence/resources. |
| ionos_prd/loki-distributed/loki-s3-sealed-secret.yaml | Adds SealedSecret providing S3 credentials via env expansion. |
| ionos_prd/grafana-dashboards/application-logs.yaml | Introduces an “Application Logs” dashboard with namespace/pod/level/search variables and overview panels. |
| applications_prd/monitoring/loki-distributed.yaml | Updates ArgoCD app to deploy the Loki S3 SealedSecret alongside the Helm release. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+119
to
+123
| "name": "level", | ||
| "label": "Level", | ||
| "type": "custom", | ||
| "query": ". : All,(?i)(warn|error|fatal|panic|exception) : Warnings and errors,(?i)(error|fatal|panic|exception) : Errors only", | ||
| "options": [ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Loki persists ~470 days of logs on prd, but only about 2 hours is queryable in Grafana.
Both things people were saying turned out to be true at once:
They reconcile because the data is persisted but unreachable.
Root cause
The
filesystemobject store requires one filesystem visible to every component. In the distributed layout the ingester and querier each hold a separate ReadWriteOnce volume:The querier searches an empty disk. Queries return
status: successwithtotalChunksRef: 0, so Grafana renders "No data" rather than an error — which is why this looked like missing logs rather than a broken backend.This cannot be fixed in place. Mounting the ingester's volume into a second pod — even
readOnly, even pinned to the same node — fails:ext4 will not mount one block device twice, and IONOS offers no RWX storage class. Distributed Loki with filesystem storage is unfixable on IONOS. Object storage removes the constraint entirely.
What changed
Storage
object_store/shared_store:filesystem→s3(bucketloki-prd,eu-central-3)schema: v11,store: boltdb-shipperand the 24h index period are unchanged — this is what makes migrated historical chunks resolve-config.expand-env=true; nothing secret in gitHigh availability
replication_factor1 → 2. Previously a single ingester restart stopped ingestion cluster-wide — observed twice (210 push-failures on 2026-07-13)Fixes carried along
query-frontendwas capped at 64Mi and OOMKilled every ~8 min (23 restarts) — this was the "intermittent Loki errors" users reported. Introduced by Infrastructure: PRD — loki-distributed resource requests #2681; now given real resources.Complete log collection
ingress-node=true:NoSchedule, so nothing oningress-pool-*was collected — including the ingress-nginx controller's HTTP access logs, exactly what you need to debug a 4xx/5xx from a component. Now 5/5.tolerationsreplaces the chart defaults, so the master/control-plane entries are carried over deliberately.Developer access — new "Application Logs" dashboard
ionos_prd/grafana-dashboardssource of kube-prometheus-stack — no Application change neededdatasourcevariable, because the Loki datasource has no fixed UID in this clusterData migration — already done and verified
1,892,200objects /122.28 GiBcovering 2025-04-18 → 2026-07-31 were copied intoloki-prd, from a snapshot clone so production was never touched. Zero failures. All 466 index tables present.Verified by running a Loki with no volume at all, reading only from S3:
Identical to the pre-migration filesystem results. (No data at 460d for any namespace — a real data boundary, not a migration gap.)
Verification done
helm templateagainst loki-distributed 0.79.0 renders clean; confirmedobject_store: s3,shared_store: s3,replication_factor: 2, and that every Loki component receives-config.expand-env=true+envFrom: loki-s3-credentialshelm templateagainst promtail 6.16.0 confirms all three tolerations reach the DaemonSetloki-prdlabel_values()variable queries executed against a Loki reading the migrated bucket — all HTTP 200 with data; 21 namespaces listed, pod lists correctly scoped per namespaceuid=dome-application-logsWorth noting what the current backend does with those same dashboard queries. Run against production Loki today, the aggregations do not merely return "No data" — they fail outright:
That is this bug reproduced from the user's side. After cutover the identical queries return 200 with data.
1. Two StatefulSets must be deleted before sync.
volumeClaimTemplatesare immutable, and this PR changes the ingester claim (200Gi → 10Gi) and removes the querier's. ArgoCD sync will otherwise fail with aForbiddenerror:Then sync. Old PVCs are retained, not deleted — they are the rollback path.
2. A delta copy is required after cutover. The migration snapshot froze at 2026-07-31 ~16:20 UTC. Anything written after that is only on the old volume. Once this is live, re-running the migration job copies just the delta (it skips objects already present, so it takes minutes). Without this, that window silently disappears from queries.
3. Rollback: revert this PR. The old 200Gi volume still holds every chunk.