# Logurile tale nu ar trebui să știe în ce cloud rulează

E 02:40 și pipeline-ul de comenzi pierde evenimente. API-ul rulează pe EKS. Worker-ul care îmbogățește comenzile s-a mutat pe GKE trimestrul trecut, pentru că echipa de ML stă acolo. Serviciul vechi de facturare e încă pe AKS. Ai trei tab-uri deschise: CloudWatch Logs Insights, Cloud Logging cu limbajul lui de interogare și Azure Monitor cu KQL. Trei sintaxe. Trei feluri de a scrie „ultimele cincisprezece minute”. CloudWatch afișează timestamp-urile în UTC, portalul Azure în ora ta locală, Google în ce spune browserul. Corelezi cu ochiul, copiezi un request ID dintr-un tab în căsuța de căutare a următorului și greșești de două ori până nimerești.

Nimeni n-a planificat asta. Fiecare echipă a luat logging-ul pe care i l-a oferit cloud-ul ei, pentru că era acolo și era preconfigurat, iar fiecare decizie a fost corectă local. Incidentul e nota de plată pentru toate deciziile astea, sosită deodată.

Instinctul e să rezolvi cu tooling: cumperi ceva care interoghează toate trei, scrii un script care trage din trei API-uri. Asta tratează simptomul. Problema e de arhitectură: ai lăsat cloud-ul să dețină logurile. Aplicația a scris pe stdout, platforma le-a preluat și din momentul ăla formatul, limbajul de interogare, retenția și prețul au fost decise de vendor. Logurile sunt un output al aplicației tale. Unde ajung ar trebui să fie decizia ta, luată o singură dată, și ar trebui să supraviețuiască următoarei schimbări de infrastructură.

## Cum ai ajuns aici

Fiecare cloud îți dă logging gratis, în sensul că nu ai nimic de instalat. Un container pe EKS scrie pe stdout și, cu un add-on, liniile apar în CloudWatch. Pe GKE agentul e deja în imaginea nodului. Pe AKS bifezi „Container Insights”. E drumul minimei rezistențe, iar pe primul cloud chiar e alegerea corectă: ai ceva funcțional într-o după-amiază și treci la produs.

Costul ascuns apare în trei momente. Al doilea cloud, când descoperi că nimic din ce ai construit pe primul nu se transferă: nici query-urile, nici dashboard-urile, nici alertele, nici reflexele. Migrarea, când un workload se mută și cele șase luni de istoric rămân în urmă, într-un store pentru care continui să plătești. Și factura de ingestion, care crește cu traficul, nu cu numărul de ingineri, și care ajunge a treia linie ca mărime pe factură înainte să observe cineva.

| | CloudWatch Logs | Cloud Logging | Azure Monitor Logs |
|---|---|---|---|
| Limbaj de interogare | Logs Insights (sintaxă proprie) | Logging query language | KQL |
| Ingestion (preț de listă) | $0,50 / GB | $0,50 / GiB, primii 50 GiB gratuit | $2,76 / GB nivel Analytics, $0,65 / GB nivel Basic |
| Retenție | $0,03 / GB-lună | 30 de zile incluse | 31 de zile incluse |
| Export | Subscription filter către Kinesis, Firehose sau Lambda; export batch în S3 | Log sinks către Pub/Sub, Cloud Storage, BigQuery | Diagnostic settings către Event Hubs sau Storage; Data Export |

Trei servicii, trei limbaje de interogare, trei modele de preț și trei răspunsuri diferite la „cum îmi scot logurile de aici”. Niciunul nu e prost. Doar că nu sunt ale tale.

## Principiul: separă emiterea de destinație

Soluția e o graniță. Împarte logging-ul în trei straturi și dă-i fiecăruia un contract pe care celelalte nu au voie să-l încalce.

**Aplicația** emite JSON structurat cu convențiile semantice OpenTelemetry. Își știe propriul nume, `service.name`, și `trace_id`-ul request-ului pe care îl procesează. Nu știe, și nu trebuie să-i pese, în ce cloud rulează. Niciun SDK pentru CloudWatch, nicio bibliotecă client pentru Cloud Logging. Stdout, sau OTLP către localhost, și nimic altceva.

**Agentul** rulează unul per host sau per cluster: un OpenTelemetry Collector sau Grafana Alloy. E singura componentă care are voie să știe de cloud. Descoperă `cloud.provider`, `cloud.region` și numele clusterului din endpoint-ul de metadata, le ștampilează pe fiecare înregistrare, face batch, comprimă și trimite. Dacă schimbi cloud-ul, e singurul lucru care se schimbă, și, cum vom vedea mai jos, nici măcar nu se schimbă mult.

**Backend-ul** e neutru: Loki pentru stocare, Grafana deasupra. Vorbește OTLP la intrare și LogQL la ieșire. Habar n-are dacă o linie de log a venit din Virginia, din Frankfurt sau de pe un laptop.

<div class="article-figure">
<svg viewBox="0 0 900 380" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Trei clustere, EKS pe AWS, GKE pe GCP și AKS pe Azure, fiecare rulând același pod api care emite JSON cu service.name și trace_id, și același DaemonSet OpenTelemetry Collector al cărui procesor resourcedetection setează cloud.provider ca aws, gcp sau azure. Toate trei colectoarele trimit OTLP peste HTTP cu compresie zstd către un singur Loki, pe care Grafana îl interoghează cu o singură expresie LogQL.">
<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">pod api</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">pod api</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">pod api</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">ingest OTLP · object storage · un singur 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>

Contractul dintre straturi e OTLP și o mână de resource attributes. Atât. Oricare dintre cele trei straturi poate fi înlocuit fără ca celelalte două să observe.

## Exemplul concret: un serviciu, trei clustere

Același serviciu `api` rulează pe EKS, GKE și AKS. Mai jos e configurația de collector desfășurată pe toate trei ca DaemonSet. E un singur fișier. Nu există niciun `if cloud == aws` nicăieri în el.

```yaml
# otel-collector.yaml — identic pe EKS, GKE și 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          # parsează formatele CRI-O / containerd / docker

processors:
  resourcedetection:
    detectors: [env, eks, gcp, aks, azure, ec2, system]
    timeout: 5s
    override: false              # nu suprascrie niciodată ce a setat deja aplicația
  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]
```

Linia interesantă e `detectors`. Collector-ul încearcă fiecare detector în ordine; cele care nu se aplică eșuează în liniște. Pe EKS detectorul `eks` setează `cloud.provider=aws`, `cloud.platform=aws_eks` și numele clusterului. Pe GKE detectorul `gcp` setează `cloud.provider=gcp`, `cloud.platform=gcp_kubernetes_engine` și `cloud.region`. Pe AKS detectorul `aks` setează `cloud.provider=azure` și `cloud.platform=azure_aks`. Aplicația n-a atins niciodată niciuna dintre valorile astea. Aceeași imagine, același manifest, trei răspunsuri diferite, toate corecte.

La recepție, Loki 3 ingerează OTLP nativ și transformă resource attributes fie în label-uri indexate, fie în structured metadata. Implicit `service.name`, `k8s.namespace.name` și `cloud.region` devin label-uri. Adăugăm `cloud.provider` la listă, pentru că „în ce cloud” e primul lucru după care grupăm în timpul unui incident:

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

Tot restul atașat de collector, cum ar fi numele pod-ului sau deployment-ul, ajunge în structured metadata: interogabil, dar neindexat, așa că numărul de label-uri rămâne mic.

Acum query-urile. Toate erorile din serviciul `api`, pe toate cele trei clouduri, într-o singură linie:

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

Același lucru, doar pe GCP:

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

Rata de erori per cloud în ultimele cinci minute, adică panoul pe care chiar îl vrei pe dashboard-ul de incident:

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

Și query-ul pe care îl făceam cu ochiul la 02:40, urmărind un singur request prin toate cele trei clustere:

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

Ce întoarce Grafana pentru ultimul, condensat:

```
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
```

Patru linii, trei clouduri, un singur format de timestamp, un singur fus orar, sortate. Serviciul de facturare de pe AKS resetează conexiunile și worker-ul de pe GKE dă timeout din cauza lui. Asta a durat unsprezece secunde de găsit, nu patruzeci de minute, și nimic din aplicație nu s-a schimbat ca să fie posibil.

Pentru dezvoltare locală și pentru o primă încercare, ăsta e tot backend-ul:

```yaml
# docker-compose.yml — Loki + Grafana, OTLP la intrare pe :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: {}
```

Îndrepți exporter-ul collector-ului către `http://localhost:3100/otlp`, deschizi Grafana pe portul 3000 și query-urile de mai sus merg neschimbate. Asta e ideea: laptopul developerului e doar o altă valoare a lui `cloud_provider`.

## Obiecția reală: egress

Cineva o va ridica la design review și va avea dreptate: logurile care ies din cloud costă bani. Fiecare provider taxează octeții care pleacă spre internet sau spre alt provider, cam $0,09–0,12 per GB. La 100 GB pe zi de loguri brute înseamnă până la $360 pe lună per cloud și pare că plătești ca să pleci.

Există trei răspunsuri oneste, fiecare cu un trade-off.

**Compresie și sampling la agent.** Logurile JSON se comprimă de cinci până la zece ori cu zstd, iar collector-ul o face înainte ca ceva să traverseze firul. Adaugă o politică de sampling care păstrează toate erorile, toate liniile care poartă un trace eșantionat și una din zece linii de debug, și cei 100 GB devin 10–20 GB. Egress-ul scade la $15–40 pe lună. Trade-off-ul e că nu mai ai fiecare linie și trebuie să decizi dinainte de care nu ai nevoie. Pentru majoritatea echipelor ăsta e răspunsul corect și singurul de care vor avea vreodată nevoie.

**Loki per cloud, Grafana federată.** Rulezi un Loki mic în fiecare cloud, care scrie în object storage-ul cloud-ului respectiv, și adaugi fiecare ca data source într-o singură Grafana. Un panou cu mixed data source interoghează toate trei și îmbină rezultatul. Egress-ul e zero: pleacă doar rezultatele query-urilor, iar acelea sunt kilobyți. Trade-off-ul: trei deployment-uri de Loki de operat, iar query-urile cross-cloud sunt un merge în Grafana, nu un singur index, deci „sortează totul după timp” merge, dar „join pe trace_id între clouduri” e mai lent. Alege varianta asta când regulile de rezidență a datelor țin oricum logurile în regiune.

**Centralizare pe cloud-ul dominant.** Dacă 80 % din workload-uri sunt pe AWS, pui Loki acolo și plătești egress pentru cele 20 %. Un index, un store, o singură cale de query, cel mai simplu de rulat. Trade-off-ul e că e cea mai scumpă opțiune și face, pe tăcute, un cloud mai egal decât celelalte, adică exact ce încercai să eviți.

<div class="article-figure">
<svg viewBox="0 0 900 280" width="100%" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Trei moduri de a trata egress-ul logurilor. Unu: compresie și sampling la agent, 100 GB pe zi devin 10–20 GB, egress cam 20–45 de dolari pe lună, trade-off-ul e că alegi ce arunci. Doi: un Loki per cloud cu o singură Grafana federată, pleacă doar rezultatele query-urilor, egress aproape zero, trade-off-ul e că operezi trei Loki. Trei: centralizare pe cloud-ul dominant, cloud-urile mai mici plătesc 9–12 cenți per GB, egress cam 180–240 de dolari pe lună pentru loguri brute, trade-off-ul e că un cloud devine mai egal decât celelalte.">
<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 · Compresie + sampling la agent</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/zi</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/zi</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 (central)</text>
<text x="150" y="222" text-anchor="middle" fill="#4fffb0" font-size="12" font-weight="700">egress ≈ $20–45 / lună</text>
<text x="150" y="248" text-anchor="middle" fill="#ffd166" font-size="11">trade-off: alegi tu ce arunci</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 per cloud + Grafana federată</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">doar rezultatele query-urilor (kilobyți)</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: trei Loki de operat</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 · Centralizare pe cloud-ul dominant</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 % din workload-uri</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 pe fiecare octet care traversează</text>
<text x="750" y="222" text-anchor="middle" fill="#ff6b8a" font-size="12" font-weight="700">egress ≈ $180–240 / lună (brut)</text>
<text x="750" y="248" text-anchor="middle" fill="#ffd166" font-size="11">trade-off: un cloud mai egal decât celelalte</text>
</g>
</svg>
</div>

Pentru 100 GB pe zi împărțiți egal între cele trei clouduri, factura de egress pentru cele trei opțiuni, la preț de listă și fără angajamente:

| Opțiune | Octeți care ies pe lună | Cost egress |
|---|---|---|
| Brut, centralizat | ~2 TB (două din trei clouduri) | ~$180–240 |
| Comprimat și eșantionat, centralizat | ~200–400 GB | ~$20–45 |
| Loki per cloud, query-uri federate | doar rezultatele query-urilor | ~$0 |

## Ce rămâne nativ, și e OK

Articolul ăsta e despre logurile aplicației. Cloud-ul tău produce și loguri de control plane: CloudTrail, VPC Flow Logs, Azure Activity Log, GCP Audit Logs. Lasă-le unde sunt. Sunt generate de platformă, sunt cele mai utile cu tooling-ul propriu al platformei (GuardDuty, Defender, Security Command Center), iar mutarea lor nu-ți aduce nimic, decât dacă un auditor cere o singură arhivă. Dacă vine ziua aia, le exporți cu un sink sau cu un subscription filter în același object storage și le interoghezi tot din Grafana. Până atunci, e o distragere.

Linia e simplă: dacă a emis-o codul tău, trece prin collector și nu știe de cloud. Dacă a emis-o cloud-ul, cloud-ul o poate păstra.

## Costul

Cifrele care fac argumentul. Ipoteze: 100 GB pe zi, retenție 30 de zile, prețuri de listă din septembrie 2026, fără discount-uri enterprise, cam 3 TB ingerați pe lună.

| Backend | Ingestion | Retenție (30 zile) | Total lunar |
|---|---|---|---|
| CloudWatch Logs, clasa Standard | $1.500 | ~$90 | ~$1.600 plus $0,005 / GB scanat de query-uri |
| Google Cloud Logging | ~$1.475 (50 GiB gratuit) | inclusă | ~$1.475 |
| Azure Monitor, nivel Analytics | $8.280 | inclusă (31 zile) | ~$8.300 |
| Azure Monitor, nivel Basic | $1.950 | inclusă | ~$1.950, cu un KQL restrâns |
| Loki pe object storage, 3 noduri | $0 | ~$10–15 (300–400 GB comprimat) | ~$350–500 compute și stocare |

Linia lui Loki e dominată de cele trei instanțe care îl rulează, iar acelea nu cresc cu volumul de loguri până nu treci bine de un terabyte pe zi. Liniile serviciilor managed cresc liniar cu fiecare octet. La 10 GB pe zi diferența e o eroare de rotunjire și ar trebui să iei varianta nativă și să-ți vezi de produs. La 100 GB pe zi plătește un inginer. La un terabyte pe zi plătește echipa.

Un asterisc: acum operezi Loki. E un cost real, în ore, care nu apare în tabel. E unul mic, Loki e un singur binar care citește și scrie în object storage, dar nu e zero, și dacă nimeni din echipă nu vrea să-l dețină, Loki-ul găzduit din Grafana Cloud stă între cele două coloane ca preț și îți ia operarea de pe cap.

## Alternative, pe scurt

Loki nu e singurul backend neutru, iar arhitecturii nu-i pasă pe care îl alegi, atâta timp cât ingerează OTLP.

**VictoriaLogs** e mai ușor decât Loki, n-are anxietăți de cardinalitate, iar limbajul lui de interogare e mai simplu. Alege-l dacă ai o echipă mică și multe câmpuri cu cardinalitate mare, gen user ID-uri. **OpenSearch** îți dă căutare full-text adevărată și un ecosistem uriaș, cu prețul unui cluster căruia îi pasă de heap și de shard-uri. Alege-l dacă căutările tale sunt chiar căutări de text, nu lookup-uri pe label-uri. **SigNoz** pune la un loc loguri, trace-uri și metrici într-un singur produs pe ClickHouse, cu UI-ul lui. Alege-l dacă vrei un singur lucru de instalat și nu ai deja Grafana.

Oricare ar fi alegerea, configul de collector de mai sus nu se schimbă. Doar endpoint-ul exporter-ului.

## Concluzie

Logurile care nu știu unde rulează sunt loguri care supraviețuiesc următoarei decizii de infrastructură. Muți un serviciu de pe EKS pe GKE și istoricul lui nu rămâne în urmă. Adaugi un al patrulea cloud, sau un cluster bare-metal la sediul unui client, și apare ca o nouă valoare a lui `cloud_provider` într-un query pe care îl ai deja. Renegociezi cu un vendor fără ca observabilitatea să fie ostatică în discuție.

N-am ajuns aici dintr-un slide deck. [Agent Factory](/ro/blog/agent-factory-platform) al nostru rulează agenți AI în mediile clienților, iar clienții au cloud-urile pe care le au. Unul e complet pe AWS, unul e pe Azure din cauza unui acord cu Microsoft, unul rulează Kubernetes on-premises și nu lasă telemetria să iasă din clădire. Dacă logurile platformei ar depinde de cloud-ul platformei, am întreține trei stack-uri de observabilitate și am corela cu ochiul la 02:40. În schimb, fiecare agent emite OTLP, un collector per cluster ștampilează cloud-ul pe el, iar noi avem un singur query pentru „arată-mi fiecare rulare de agent eșuată din ultima oră” care merge peste tot.

Cloud-ul e un detaliu de infrastructură. Tratează-l ca atare. Dacă vrei ajutor să trasezi granița asta în stack-ul tău, [hai să vorbim](/contact).
