# Ihre Logs sollten nicht wissen, in welcher Cloud sie laufen

Es ist 02:40 Uhr und die Bestell-Pipeline verliert Events. Die API läuft auf EKS. Der Worker, der Bestellungen anreichert, ist letztes Quartal nach GKE umgezogen, weil das ML-Team dort zu Hause ist. Der alte Abrechnungsdienst liegt noch auf AKS. Sie haben drei Browser-Tabs offen: CloudWatch Logs Insights, Cloud Logging mit seiner eigenen Abfragesprache und Azure Monitor mit KQL. Drei Syntaxen. Drei Schreibweisen für „die letzten fünfzehn Minuten“. CloudWatch zeigt Zeitstempel in UTC, das Azure-Portal in Ihrer Ortszeit, Google in dem, was der Browser sagt. Sie korrelieren mit dem Auge, kopieren eine Request-ID aus einem Tab in das Suchfeld des nächsten und liegen zweimal daneben, bevor Sie richtig liegen.

Niemand hat das geplant. Jedes Team hat das Logging genommen, das seine Cloud angeboten hat, weil es da war und vorkonfiguriert war, und jede Entscheidung war für sich genommen richtig. Der Vorfall ist die Rechnung für all diese Entscheidungen, die auf einmal eintrifft.

Der Instinkt sagt: mit Tooling lösen. Etwas kaufen, das alle drei abfragt, ein Skript schreiben, das aus drei APIs zieht. Das behandelt das Symptom. Das Problem ist architektonisch: Sie haben der Cloud erlaubt, Ihre Logs zu besitzen. Die Anwendung hat nach stdout geschrieben, die Plattform hat es aufgesammelt, und ab diesem Moment wurden Format, Abfragesprache, Aufbewahrung und Preis vom Anbieter entschieden. Logs sind ein Output Ihrer Anwendung. Wo sie landen, sollte Ihre Entscheidung sein, einmal getroffen, und sie sollte die nächste Infrastrukturänderung überleben.

## Wie Sie hierher gekommen sind

Jede Cloud gibt Ihnen Logging umsonst, in dem Sinne, dass nichts zu installieren ist. Ein Container auf EKS schreibt nach stdout, und mit einem Add-on erscheinen die Zeilen in CloudWatch. Auf GKE steckt der Agent bereits im Node-Image. Auf AKS setzen Sie ein Häkchen bei „Container Insights“. Es ist der Weg des geringsten Widerstands, und bei der ersten Cloud ist es tatsächlich die richtige Entscheidung: Sie haben an einem Nachmittag etwas Funktionierendes und wenden sich dem Produkt zu.

Die versteckten Kosten zeigen sich in drei Momenten. Bei der zweiten Cloud, wenn Sie feststellen, dass nichts von dem, was Sie auf der ersten gebaut haben, übertragbar ist: nicht die Queries, nicht die Dashboards, nicht die Alerts, nicht das Muskelgedächtnis. Bei der Migration, wenn ein Workload umzieht und seine sechs Monate Historie in einem Speicher zurückbleiben, für den Sie weiter bezahlen. Und bei der Ingestion-Rechnung, die mit Ihrem Traffic wächst statt mit Ihrer Engineering-Mannschaft und die dazu neigt, der drittgrößte Posten auf der Rechnung zu werden, bevor es jemand bemerkt.

| | CloudWatch Logs | Cloud Logging | Azure Monitor Logs |
|---|---|---|---|
| Abfragesprache | Logs Insights (eigene Syntax) | Logging query language | KQL |
| Ingestion (Listenpreis) | $0,50 / GB | $0,50 / GiB, erste 50 GiB kostenlos | $2,76 / GB Analytics-Stufe, $0,65 / GB Basic-Stufe |
| Aufbewahrung | $0,03 / GB-Monat | 30 Tage inklusive | 31 Tage inklusive |
| Export | Subscription Filter nach Kinesis, Firehose oder Lambda; Batch-Export nach S3 | Log Sinks nach Pub/Sub, Cloud Storage, BigQuery | Diagnostic Settings nach Event Hubs oder Storage; Data Export |

Drei Dienste, drei Abfragesprachen, drei Preismodelle und drei verschiedene Antworten auf „wie bekomme ich meine Logs hier raus“. Keiner davon ist schlecht. Sie gehören nur nicht Ihnen.

## Das Prinzip: Emittieren von Speichern trennen

Die Lösung ist eine Grenze. Teilen Sie das Logging in drei Schichten und geben Sie jeder einen Vertrag, den die anderen nicht brechen dürfen.

**Die Anwendung** emittiert strukturiertes JSON mit den semantischen Konventionen von OpenTelemetry. Sie kennt ihren eigenen Namen, `service.name`, und die `trace_id` des Requests, den sie gerade bearbeitet. Sie weiß nicht, und es darf sie nicht kümmern, in welcher Cloud sie läuft. Kein SDK für CloudWatch, keine Client-Bibliothek für Cloud Logging. Stdout, oder OTLP nach localhost, und sonst nichts.

**Der Agent** läuft einmal pro Host oder Cluster: ein OpenTelemetry Collector oder Grafana Alloy. Das ist die einzige Komponente, die von der Cloud wissen darf. Sie ermittelt `cloud.provider`, `cloud.region` und den Clusternamen über den Metadata-Endpunkt, stempelt sie auf jeden Datensatz, bündelt, komprimiert und verschickt. Wenn Sie die Cloud wechseln, ist das das Einzige, was sich ändert, und wie wir unten sehen werden, ändert es sich nicht einmal viel.

**Das Backend** ist neutral: Loki für die Speicherung, Grafana obendrauf. Es spricht OTLP hinein und LogQL heraus. Es hat keine Ahnung, ob eine Logzeile aus Virginia, Frankfurt oder von einem Laptop kam.

<div class="article-figure">
<svg viewBox="0 0 900 380" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Drei Cluster, EKS auf AWS, GKE auf GCP und AKS auf Azure, jeder mit demselben api-Pod, der JSON mit service.name und trace_id emittiert, und demselben OpenTelemetry-Collector-DaemonSet, dessen resourcedetection-Prozessor cloud.provider als aws, gcp oder azure setzt. Alle drei Collectoren senden OTLP über HTTP mit zstd-Kompression an einen einzigen Loki, den Grafana mit einem einzigen LogQL-Ausdruck abfragt.">
<defs><marker id="arrL1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#4fffb0"/></marker></defs>
<g font-family="Inter,system-ui,sans-serif">
<rect x="30" y="20" width="240" height="190" rx="14" fill="#151b2e" stroke="#2a3150" stroke-width="1.5"/>
<text x="150" y="46" text-anchor="middle" fill="#ffd166" font-size="14" font-weight="700">AWS · EKS</text>
<rect x="50" y="62" width="200" height="50" rx="8" fill="#0d1120" stroke="#7b8cff" stroke-width="1.5"/>
<text x="150" y="82" text-anchor="middle" fill="#f1f3ff" font-size="12">api-Pod</text>
<text x="150" y="100" text-anchor="middle" fill="#9aa3c7" font-size="11">JSON + service.name + trace_id</text>
<line x1="150" y1="112" x2="150" y2="130" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL1)"/>
<rect x="50" y="132" width="200" height="62" rx="8" fill="#0d1120" stroke="#4fffb0" stroke-width="1.5"/>
<text x="150" y="152" text-anchor="middle" fill="#f1f3ff" font-size="12">OTel Collector (DaemonSet)</text>
<text x="150" y="170" text-anchor="middle" fill="#9aa3c7" font-size="11">resourcedetection →</text>
<text x="150" y="186" text-anchor="middle" fill="#4fffb0" font-size="11">cloud.provider=aws</text>
<rect x="330" y="20" width="240" height="190" rx="14" fill="#151b2e" stroke="#2a3150" stroke-width="1.5"/>
<text x="450" y="46" text-anchor="middle" fill="#ffd166" font-size="14" font-weight="700">GCP · GKE</text>
<rect x="350" y="62" width="200" height="50" rx="8" fill="#0d1120" stroke="#7b8cff" stroke-width="1.5"/>
<text x="450" y="82" text-anchor="middle" fill="#f1f3ff" font-size="12">api-Pod</text>
<text x="450" y="100" text-anchor="middle" fill="#9aa3c7" font-size="11">JSON + service.name + trace_id</text>
<line x1="450" y1="112" x2="450" y2="130" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL1)"/>
<rect x="350" y="132" width="200" height="62" rx="8" fill="#0d1120" stroke="#4fffb0" stroke-width="1.5"/>
<text x="450" y="152" text-anchor="middle" fill="#f1f3ff" font-size="12">OTel Collector (DaemonSet)</text>
<text x="450" y="170" text-anchor="middle" fill="#9aa3c7" font-size="11">resourcedetection →</text>
<text x="450" y="186" text-anchor="middle" fill="#4fffb0" font-size="11">cloud.provider=gcp</text>
<rect x="630" y="20" width="240" height="190" rx="14" fill="#151b2e" stroke="#2a3150" stroke-width="1.5"/>
<text x="750" y="46" text-anchor="middle" fill="#ffd166" font-size="14" font-weight="700">Azure · AKS</text>
<rect x="650" y="62" width="200" height="50" rx="8" fill="#0d1120" stroke="#7b8cff" stroke-width="1.5"/>
<text x="750" y="82" text-anchor="middle" fill="#f1f3ff" font-size="12">api-Pod</text>
<text x="750" y="100" text-anchor="middle" fill="#9aa3c7" font-size="11">JSON + service.name + trace_id</text>
<line x1="750" y1="112" x2="750" y2="130" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL1)"/>
<rect x="650" y="132" width="200" height="62" rx="8" fill="#0d1120" stroke="#4fffb0" stroke-width="1.5"/>
<text x="750" y="152" text-anchor="middle" fill="#f1f3ff" font-size="12">OTel Collector (DaemonSet)</text>
<text x="750" y="170" text-anchor="middle" fill="#9aa3c7" font-size="11">resourcedetection →</text>
<text x="750" y="186" text-anchor="middle" fill="#4fffb0" font-size="11">cloud.provider=azure</text>
<path d="M150,210 C150,250 430,230 440,262" fill="none" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL1)"/>
<path d="M450,210 L450,262" fill="none" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL1)"/>
<path d="M750,210 C750,250 470,230 460,262" fill="none" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL1)"/>
<text x="270" y="248" text-anchor="middle" fill="#9aa3c7" font-size="11">OTLP/HTTP · zstd</text>
<text x="630" y="248" text-anchor="middle" fill="#9aa3c7" font-size="11">OTLP/HTTP · zstd</text>
<rect x="320" y="264" width="260" height="46" rx="10" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/>
<text x="450" y="284" text-anchor="middle" fill="#f1f3ff" font-size="13" font-weight="700">Loki</text>
<text x="450" y="301" text-anchor="middle" fill="#9aa3c7" font-size="11">OTLP-Ingest · Object Storage · ein Index</text>
<line x1="450" y1="310" x2="450" y2="326" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL1)"/>
<rect x="320" y="328" width="260" height="40" rx="10" fill="#151b2e" stroke="#7b8cff" stroke-width="1.5"/>
<text x="450" y="346" text-anchor="middle" fill="#f1f3ff" font-size="12" font-weight="700">Grafana</text>
<text x="450" y="361" text-anchor="middle" fill="#7b8cff" font-size="11">{service_name="api"} |= "error"</text>
</g>
</svg>
</div>

Der Vertrag zwischen den Schichten ist OTLP plus eine Handvoll Resource-Attribute. Das ist alles. Jede der drei Schichten kann ausgetauscht werden, ohne dass die anderen beiden es bemerken.

## Das konkrete Beispiel: ein Dienst, drei Cluster

Derselbe `api`-Dienst läuft auf EKS, GKE und AKS. Unten steht die Collector-Konfiguration, die auf allen dreien als DaemonSet ausgerollt ist. Es ist eine Datei. Nirgendwo darin steht ein `if cloud == aws`.

```yaml
# otel-collector.yaml — identisch auf EKS, GKE und AKS
receivers:
  otlp:
    protocols:
      grpc: { endpoint: 0.0.0.0:4317 }
      http: { endpoint: 0.0.0.0:4318 }
  filelog:
    include: [/var/log/pods/*/*/*.log]
    operators:
      - type: container          # parst CRI-O- / containerd- / docker-Zeilenformate

processors:
  resourcedetection:
    detectors: [env, eks, gcp, aks, azure, ec2, system]
    timeout: 5s
    override: false              # nie überschreiben, was die App schon gesetzt hat
  k8sattributes:
    extract:
      metadata: [k8s.namespace.name, k8s.deployment.name, k8s.pod.name]
  batch:
    timeout: 5s
    send_batch_size: 4096

exporters:
  otlphttp/loki:
    endpoint: https://loki.observability.internal/otlp
    compression: zstd
    headers:
      X-Scope-OrgID: platform

service:
  pipelines:
    logs:
      receivers: [otlp, filelog]
      processors: [resourcedetection, k8sattributes, batch]
      exporters: [otlphttp/loki]
```

Die interessante Zeile ist `detectors`. Der Collector probiert jeden Detektor der Reihe nach; die, die nicht zutreffen, scheitern leise. Auf EKS setzt der `eks`-Detektor `cloud.provider=aws`, `cloud.platform=aws_eks` und den Clusternamen. Auf GKE setzt der `gcp`-Detektor `cloud.provider=gcp`, `cloud.platform=gcp_kubernetes_engine` und `cloud.region`. Auf AKS setzt der `aks`-Detektor `cloud.provider=azure` und `cloud.platform=azure_aks`. Die Anwendung hat keinen dieser Werte je angefasst. Dasselbe Image, dasselbe Manifest, drei verschiedene Antworten, alle richtig.

Auf der Empfängerseite nimmt Loki 3 OTLP nativ entgegen und macht aus Resource-Attributen entweder Index-Labels oder Structured Metadata. Standardmäßig werden `service.name`, `k8s.namespace.name` und `cloud.region` zu Labels. Wir fügen `cloud.provider` zu dieser Liste hinzu, weil „welche Cloud“ das Erste ist, wonach wir bei einem Vorfall gruppieren:

```yaml
# loki.yaml (Auszug)
limits_config:
  otlp_config:
    resource_attributes:
      attributes_config:
        - action: index_label
          attributes: [cloud.provider, cloud.platform]
```

Alles andere, was der Collector angehängt hat, etwa der Pod-Name oder das Deployment, landet in Structured Metadata: abfragbar, aber nicht indiziert, sodass die Label-Kardinalität flach bleibt.

Nun die Queries. Alle Fehler des `api`-Dienstes, über alle drei Clouds, in einer Zeile:

```logql
{service_name="api"} |= "error"
```

Dasselbe, nur auf GCP:

```logql
{service_name="api", cloud_provider="gcp"} |= "error"
```

Fehlerrate pro Cloud über die letzten fünf Minuten, also das Panel, das Sie auf dem Incident-Dashboard wirklich wollen:

```logql
sum by (cloud_provider) (
  rate({service_name="api"} | json | level="error" [5m])
)
```

Und die Query, die wir um 02:40 Uhr mit dem Auge gemacht haben, einem einzelnen Request über alle drei Cluster hinweg folgend:

```logql
{service_name=~"api|order-worker|billing"} | json | trace_id="4bf92f3577b34da6a3ce929d0e0e4736"
```

Was Grafana für die letzte zurückgibt, verdichtet:

```
2026-09-03 02:41:07.113  cloud_provider=aws    service_name=api           POST /orders 202                       trace_id=4bf9…4736
2026-09-03 02:41:07.201  cloud_provider=gcp    service_name=order-worker  enrich start order_id=88231            trace_id=4bf9…4736
2026-09-03 02:41:09.870  cloud_provider=gcp    service_name=order-worker  level=error upstream timeout billing   trace_id=4bf9…4736
2026-09-03 02:41:09.871  cloud_provider=azure  service_name=billing       level=error connection reset by peer   trace_id=4bf9…4736
```

Vier Zeilen, drei Clouds, ein Zeitstempelformat, eine Zeitzone, sortiert. Der Abrechnungsdienst auf AKS setzt Verbindungen zurück, und der Worker auf GKE läuft deswegen in Timeouts. Das zu finden hat elf Sekunden gedauert statt vierzig Minuten, und an der Anwendung hat sich nichts geändert, um das möglich zu machen.

Für die lokale Entwicklung und für einen ersten Versuch ist das das ganze Backend:

```yaml
# docker-compose.yml — Loki + Grafana, OTLP-Eingang auf :3100/otlp
services:
  loki:
    image: grafana/loki:3.5
    command: -config.file=/etc/loki/loki.yaml
    ports: ["3100:3100"]
    volumes:
      - ./loki.yaml:/etc/loki/loki.yaml:ro
      - loki-data:/loki

  grafana:
    image: grafana/grafana:12.1
    ports: ["3000:3000"]
    environment:
      GF_AUTH_ANONYMOUS_ENABLED: "true"
      GF_AUTH_ANONYMOUS_ORG_ROLE: Admin
    volumes:
      - ./grafana-datasource.yaml:/etc/grafana/provisioning/datasources/loki.yaml:ro
    depends_on: [loki]

volumes:
  loki-data: {}
```

Richten Sie den Exporter des Collectors auf `http://localhost:3100/otlp`, öffnen Sie Grafana auf Port 3000, und die Queries oben funktionieren unverändert. Das ist der Punkt: der Laptop des Entwicklers ist einfach ein weiterer Wert von `cloud_provider`.

## Der echte Einwand: Egress

Jemand wird ihn im Design-Review vorbringen, und er wird recht haben: Logs, die eine Cloud verlassen, kosten Geld. Jeder Anbieter berechnet Bytes, die ins Internet oder zu einem anderen Anbieter gehen, grob $0,09 bis $0,12 pro GB. Bei 100 GB Rohlogs pro Tag sind das bis zu $360 im Monat pro Cloud, und es fühlt sich an, als würde man fürs Gehen bezahlen.

Es gibt drei ehrliche Antworten, jede mit einem Trade-off.

**Am Agenten komprimieren und samplen.** JSON-Logs komprimieren mit zstd fünf- bis zehnfach, und der Collector tut das, bevor irgendetwas über die Leitung geht. Ergänzen Sie eine Sampling-Policy, die alle Fehler, alle Zeilen mit einem gesampelten Trace und jede zehnte Debug-Zeile behält, und aus Ihren 100 GB werden 10 bis 20 GB. Egress fällt auf $15–40 im Monat. Der Trade-off: Sie haben nicht mehr jede Zeile und müssen vorab entscheiden, welche Sie nicht brauchen. Für die meisten Teams ist das die richtige Antwort und die einzige, die sie je brauchen werden.

**Loki pro Cloud, föderiertes Grafana.** Betreiben Sie ein kleines Loki in jeder Cloud, das in den Object Storage dieser Cloud schreibt, und fügen Sie jedes als Data Source in einem einzigen Grafana hinzu. Ein Mixed-Data-Source-Panel fragt alle drei ab und führt das Ergebnis zusammen. Egress ist null: nur Query-Ergebnisse verlassen die Cloud, und das sind Kilobytes. Der Trade-off: drei Loki-Deployments zu betreiben, und Cloud-übergreifende Queries sind ein Merge in Grafana statt ein einziger Index, sodass „alles nach Zeit sortieren“ funktioniert, aber „Join auf trace_id über Clouds hinweg“ langsamer ist. Wählen Sie das, wenn Datenresidenz-Regeln die Logs ohnehin in der Region halten.

**Auf der dominanten Cloud zentralisieren.** Wenn 80 % Ihrer Workloads auf AWS liegen, stellen Sie Loki dorthin und zahlen Egress für die 20 %. Ein Index, ein Speicher, ein Abfragepfad, am einfachsten zu betreiben. Der Trade-off: es ist die teuerste Option, und sie macht stillschweigend eine Cloud gleicher als die anderen, was Sie ja gerade vermeiden wollten.

<div class="article-figure">
<svg viewBox="0 0 900 280" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Drei Wege, mit Log-Egress umzugehen. Eins: am Agenten komprimieren und samplen, aus 100 GB pro Tag werden 10 bis 20 GB, Egress etwa 20 bis 45 Dollar im Monat, Trade-off ist, dass Sie wählen, was verworfen wird. Zwei: ein Loki pro Cloud mit einem einzigen föderierten Grafana, nur Query-Ergebnisse verlassen die Cloud, Egress etwa null, Trade-off sind drei zu betreibende Lokis. Drei: auf der dominanten Cloud zentralisieren, die kleineren Clouds zahlen 9 bis 12 Cent pro GB, Egress etwa 180 bis 240 Dollar im Monat für Rohlogs, Trade-off ist, dass eine Cloud gleicher wird als die anderen.">
<defs><marker id="arrL2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#4fffb0"/></marker></defs>
<g font-family="Inter,system-ui,sans-serif">
<rect x="20" y="15" width="260" height="250" rx="14" fill="#151b2e" stroke="#2a3150" stroke-width="1.5"/>
<text x="150" y="42" text-anchor="middle" fill="#ffd166" font-size="13" font-weight="700">1 · Am Agenten komprimieren + samplen</text>
<rect x="38" y="70" width="96" height="40" rx="8" fill="#0d1120" stroke="#ff6b8a" stroke-width="1.5"/>
<text x="86" y="94" text-anchor="middle" fill="#f1f3ff" font-size="12">100 GB/Tag</text>
<line x1="136" y1="90" x2="166" y2="90" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL2)"/>
<text x="151" y="64" text-anchor="middle" fill="#9aa3c7" font-size="10">zstd + Sampling</text>
<rect x="168" y="70" width="94" height="40" rx="8" fill="#0d1120" stroke="#4fffb0" stroke-width="1.5"/>
<text x="215" y="94" text-anchor="middle" fill="#f1f3ff" font-size="12">10–20 GB/Tag</text>
<line x1="215" y1="112" x2="215" y2="140" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL2)"/>
<text x="232" y="130" text-anchor="start" fill="#9aa3c7" font-size="10">Egress</text>
<rect x="70" y="142" width="160" height="36" rx="8" fill="#0d1120" stroke="#ffd166" stroke-width="1.5"/>
<text x="150" y="164" text-anchor="middle" fill="#f1f3ff" font-size="12">Loki (zentral)</text>
<text x="150" y="222" text-anchor="middle" fill="#4fffb0" font-size="12" font-weight="700">Egress ≈ $20–45 / Monat</text>
<text x="150" y="248" text-anchor="middle" fill="#ffd166" font-size="11">Trade-off: Sie wählen, was wegfällt</text>
<rect x="320" y="15" width="260" height="250" rx="14" fill="#151b2e" stroke="#2a3150" stroke-width="1.5"/>
<text x="450" y="42" text-anchor="middle" fill="#ffd166" font-size="13" font-weight="700">2 · Loki pro Cloud + föderiertes Grafana</text>
<rect x="400" y="60" width="100" height="34" rx="8" fill="#0d1120" stroke="#7b8cff" stroke-width="1.5"/>
<text x="450" y="82" text-anchor="middle" fill="#f1f3ff" font-size="12">Grafana</text>
<rect x="335" y="140" width="70" height="40" rx="8" fill="#0d1120" stroke="#ffd166" stroke-width="1.5"/>
<text x="370" y="158" text-anchor="middle" fill="#f1f3ff" font-size="11">Loki</text>
<text x="370" y="172" text-anchor="middle" fill="#9aa3c7" font-size="10">aws</text>
<rect x="415" y="140" width="70" height="40" rx="8" fill="#0d1120" stroke="#ffd166" stroke-width="1.5"/>
<text x="450" y="158" text-anchor="middle" fill="#f1f3ff" font-size="11">Loki</text>
<text x="450" y="172" text-anchor="middle" fill="#9aa3c7" font-size="10">gcp</text>
<rect x="495" y="140" width="70" height="40" rx="8" fill="#0d1120" stroke="#ffd166" stroke-width="1.5"/>
<text x="530" y="158" text-anchor="middle" fill="#f1f3ff" font-size="11">Loki</text>
<text x="530" y="172" text-anchor="middle" fill="#9aa3c7" font-size="10">azure</text>
<line x1="370" y1="138" x2="440" y2="96" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL2)"/>
<line x1="450" y1="138" x2="450" y2="96" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL2)"/>
<line x1="530" y1="138" x2="460" y2="96" stroke="#4fffb0" stroke-width="1.5" marker-end="url(#arrL2)"/>
<text x="450" y="200" text-anchor="middle" fill="#9aa3c7" font-size="10">nur Query-Ergebnisse (Kilobytes)</text>
<text x="450" y="222" text-anchor="middle" fill="#4fffb0" font-size="12" font-weight="700">Egress ≈ $0</text>
<text x="450" y="248" text-anchor="middle" fill="#ffd166" font-size="11">Trade-off: drei Lokis zu betreiben</text>
<rect x="620" y="15" width="260" height="250" rx="14" fill="#151b2e" stroke="#2a3150" stroke-width="1.5"/>
<text x="750" y="42" text-anchor="middle" fill="#ffd166" font-size="13" font-weight="700">3 · Auf der dominanten Cloud zentralisieren</text>
<rect x="640" y="60" width="220" height="70" rx="10" fill="#0d1120" stroke="#2a3150" stroke-width="1.5"/>
<text x="655" y="80" text-anchor="start" fill="#9aa3c7" font-size="11">AWS · 80 % der Workloads</text>
<rect x="740" y="90" width="100" height="32" rx="8" fill="#151b2e" stroke="#ffd166" stroke-width="1.5"/>
<text x="790" y="110" text-anchor="middle" fill="#f1f3ff" font-size="12">Loki</text>
<rect x="640" y="150" width="90" height="34" rx="8" fill="#0d1120" stroke="#7b8cff" stroke-width="1.5"/>
<text x="685" y="171" text-anchor="middle" fill="#f1f3ff" font-size="11">GKE · 10 %</text>
<rect x="770" y="150" width="90" height="34" rx="8" fill="#0d1120" stroke="#7b8cff" stroke-width="1.5"/>
<text x="815" y="171" text-anchor="middle" fill="#f1f3ff" font-size="11">AKS · 10 %</text>
<line x1="700" y1="148" x2="770" y2="124" stroke="#ff6b8a" stroke-width="1.5" marker-end="url(#arrL2)"/>
<line x1="812" y1="148" x2="800" y2="124" stroke="#ff6b8a" stroke-width="1.5" marker-end="url(#arrL2)"/>
<text x="750" y="200" text-anchor="middle" fill="#ff6b8a" font-size="10">$0,09–0,12 / GB für jedes Byte, das die Grenze quert</text>
<text x="750" y="222" text-anchor="middle" fill="#ff6b8a" font-size="12" font-weight="700">Egress ≈ $180–240 / Monat (roh)</text>
<text x="750" y="248" text-anchor="middle" fill="#ffd166" font-size="11">Trade-off: eine Cloud gleicher als die anderen</text>
</g>
</svg>
</div>

Für 100 GB pro Tag, gleichmäßig auf die drei Clouds verteilt, die Egress-Rechnung für die drei Optionen, zum Listenpreis und ohne Commitments:

| Option | Bytes, die pro Monat abfließen | Egress-Kosten |
|---|---|---|
| Roh, zentralisiert | ~2 TB (zwei von drei Clouds) | ~$180–240 |
| Komprimiert und gesampelt, zentralisiert | ~200–400 GB | ~$20–45 |
| Loki pro Cloud, föderierte Queries | nur Query-Ergebnisse | ~$0 |

## Was nativ bleibt, und das ist in Ordnung

Dieser Artikel handelt von Anwendungslogs. Ihre Cloud produziert auch Control-Plane-Logs: CloudTrail, VPC Flow Logs, Azure Activity Log, GCP Audit Logs. Lassen Sie die, wo sie sind. Sie werden von der Plattform erzeugt, sie sind mit dem Tooling der Plattform am nützlichsten (GuardDuty, Defender, Security Command Center), und sie zu verschieben bringt Ihnen nichts, es sei denn, ein Auditor verlangt ein einziges Archiv. Wenn dieser Tag kommt, exportieren Sie sie per Sink oder Subscription Filter in denselben Object Storage und fragen sie ebenfalls aus Grafana ab. Bis dahin ist es eine Ablenkung.

Die Linie ist einfach: Wenn Ihr Code es emittiert hat, geht es durch den Collector und weiß nichts von der Cloud. Wenn die Cloud es emittiert hat, darf die Cloud es behalten.

## Die Kosten

Die Zahlen, die das Argument machen. Annahmen: 100 GB pro Tag, 30 Tage Aufbewahrung, Listenpreise im September 2026, keine Enterprise-Rabatte, grob 3 TB Ingestion pro Monat.

| Backend | Ingestion | Aufbewahrung (30 Tage) | Monatssumme |
|---|---|---|---|
| CloudWatch Logs, Standard-Klasse | $1.500 | ~$90 | ~$1.600 plus $0,005 / GB, die Queries scannen |
| Google Cloud Logging | ~$1.475 (50 GiB kostenlos) | inklusive | ~$1.475 |
| Azure Monitor, Analytics-Stufe | $8.280 | inklusive (31 Tage) | ~$8.300 |
| Azure Monitor, Basic-Stufe | $1.950 | inklusive | ~$1.950, mit eingeschränktem KQL |
| Loki auf Object Storage, 3 Knoten | $0 | ~$10–15 (300–400 GB komprimiert) | ~$350–500 Compute und Speicher |

Lokis Zeile wird von den drei Instanzen dominiert, die es betreiben, und die wachsen nicht mit Ihrem Logvolumen, bis Sie deutlich über einem Terabyte pro Tag liegen. Die Zeilen der Managed Services wachsen linear mit jedem Byte. Bei 10 GB pro Tag ist der Unterschied ein Rundungsfehler, und Sie sollten die native Option nehmen und sich um Ihr Produkt kümmern. Bei 100 GB pro Tag bezahlt er einen Engineer. Bei einem Terabyte pro Tag bezahlt er das Team.

Ein Sternchen: Sie betreiben jetzt Loki. Das sind echte Kosten, in Stunden, nicht in dieser Tabelle. Es sind kleine, Loki ist ein einzelnes Binary, das Object Storage liest und schreibt, aber sie sind nicht null, und wenn niemand im Team es besitzen will, liegt das gehostete Loki von Grafana Cloud preislich zwischen den beiden Spalten und nimmt Ihnen den Betrieb ab.

## Alternativen, kurz

Loki ist nicht das einzige neutrale Backend, und der Architektur ist egal, welches Sie wählen, solange es OTLP entgegennimmt.

**VictoriaLogs** ist leichter als Loki, kennt keine Kardinalitätsangst, und seine Abfragesprache ist einfacher. Wählen Sie es bei einem kleinen Team und vielen hochkardinalen Feldern wie User-IDs. **OpenSearch** gibt Ihnen echte Volltextsuche und ein riesiges Ökosystem, zum Preis eines Clusters, der sich um Heap und Shards kümmert. Wählen Sie es, wenn Ihre Suchen wirklich Textsuchen sind und keine Label-Lookups. **SigNoz** bündelt Logs, Traces und Metriken in einem Produkt auf ClickHouse-Basis mit eigener Oberfläche. Wählen Sie es, wenn Sie eine einzige Sache installieren wollen und noch kein Grafana haben.

Was immer Sie wählen, die Collector-Konfiguration oben ändert sich nicht. Nur der Exporter-Endpunkt.

## Fazit

Logs, die nicht wissen, wo sie laufen, sind Logs, die die nächste Infrastrukturentscheidung überleben. Verschieben Sie einen Dienst von EKS nach GKE, und seine Historie bleibt nicht zurück. Fügen Sie eine vierte Cloud hinzu, oder einen Bare-Metal-Cluster beim Kunden vor Ort, und er erscheint als neuer Wert von `cloud_provider` in einer Query, die Sie schon haben. Verhandeln Sie mit einem Anbieter neu, ohne dass Ihre Observability in dem Gespräch zur Geisel wird.

Wir sind nicht über ein Foliendeck hierher gekommen. Unsere [Agent Factory](/de/blog/agent-factory-platform) betreibt KI-Agenten in den Umgebungen von Kunden, und Kunden haben die Clouds, die sie haben. Einer ist komplett auf AWS, einer ist wegen eines Microsoft-Vertrags auf Azure, einer betreibt Kubernetes on-premises und lässt keine Telemetrie das Gebäude verlassen. Hingen die Logs der Plattform von der Cloud der Plattform ab, würden wir drei Observability-Stacks pflegen und um 02:40 Uhr mit dem Auge korrelieren. Stattdessen emittiert jeder Agent OTLP, ein Collector pro Cluster stempelt die Cloud darauf, und wir haben eine einzige Query für „zeig mir jeden fehlgeschlagenen Agentenlauf der letzten Stunde“, die überall funktioniert.

Die Cloud ist ein Infrastrukturdetail. Behandeln Sie sie als eines. Wenn Sie Hilfe dabei möchten, diese Grenze in Ihrem eigenen Stack zu ziehen, [sprechen Sie mit uns](/contact).
