diff --git a/content/de/developer/integration/observability/images/rustfs-loki-chunks.png b/content/de/developer/integration/observability/images/rustfs-loki-chunks.png new file mode 100644 index 00000000..96f6b9f2 Binary files /dev/null and b/content/de/developer/integration/observability/images/rustfs-loki-chunks.png differ diff --git a/content/de/developer/integration/observability/images/rustfs-tempo-blocks.png b/content/de/developer/integration/observability/images/rustfs-tempo-blocks.png new file mode 100644 index 00000000..d8d3d52e Binary files /dev/null and b/content/de/developer/integration/observability/images/rustfs-tempo-blocks.png differ diff --git a/content/de/developer/integration/observability/index.md b/content/de/developer/integration/observability/index.md index c86b25d5..2290000a 100644 --- a/content/de/developer/integration/observability/index.md +++ b/content/de/developer/integration/observability/index.md @@ -8,5 +8,7 @@ Nutzen Sie **RustFS** als Objektspeicher-Layer für Observability-Plattformen, d ## Plattformen - [OpenObserve](./openobserve.md) +- [Loki](./loki.md) +- [Tempo](./tempo.md) Speichern Sie Telemetriedaten in einem dedizierten Bucket und beschränken Sie die Anmeldeinformationen auf die erforderlichen Bucket-Operationen. diff --git a/content/de/developer/integration/observability/loki.md b/content/de/developer/integration/observability/loki.md new file mode 100644 index 00000000..668bedb4 --- /dev/null +++ b/content/de/developer/integration/observability/loki.md @@ -0,0 +1,295 @@ +--- +title: "Loki" +description: "Betreiben Sie Grafana Loki mit RustFS als S3-Objektspeicher-Backend, bereitgestellt mit Docker Compose." +--- + +Diese Anleitung betreibt [Grafana Loki](https://github.com/grafana/loki) — das Log-Aggregationssystem von Grafana Labs — mit **RustFS** als Objektspeicher-Backend. Sie starten einen Single-Binary-Loki mit Docker Compose, pushen Log-Streams über die HTTP-API, fragen sie ab und prüfen, dass die Log-Chunks als Objekte in RustFS gespeichert werden. Der Ablauf wurde mit `grafana/loki:latest` (v3.7.8) und `rustfs/rustfs-x86-musl:v2.3.1` verifiziert. + +Sie benötigen Docker mit dem Compose-Plugin. Dieses Setup ist für lokale Integrationstests gedacht, nicht für den Produktivbetrieb. + +## Architektur + +```mermaid +flowchart LR + Client["Log producer"] -->|"POST /loki/api/v1/push"| Loki["Loki :3100"] + Loki -->|"chunks + index"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Loki nimmt Log-Streams in einen In-Memory-Chunk und ein Write-Ahead-Log auf, überträgt komprimierte Chunks in den Objektspeicher, sobald ein Stream inaktiv wird, und lädt TSDB-Indexdateien in denselben Bucket. Abfragen lösen Chunks über den Index auf und lesen sie aus dem Objektspeicher. + +## 1. Projektdateien anlegen + +Erstellen Sie ein Arbeitsverzeichnis: + +```bash +mkdir rustfs-loki +cd rustfs-loki +``` + +Erstellen Sie eine Umgebungsdatei und ersetzen Sie beide Platzhalter für die Anmeldeinformationen: + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +Verwenden Sie dedizierte Anmeldeinformationen für den Bucket. Committen Sie `.env` nicht in die Versionsverwaltung. + +Erstellen Sie die Loki-Konfiguration — ein Single-Binary-Setup mit dem TSDB-Schema und dem S3-Backend, das auf RustFS zeigt: + +```yaml title="loki.yml" +auth_enabled: false + +server: + http_listen_port: 3100 + +common: + instance_addr: 127.0.0.1 + path_prefix: /loki + storage: + s3: + endpoint: rustfs:9000 + insecure: true + bucketnames: my-bucket + access_key_id: ${RUSTFS_ACCESS_KEY} + secret_access_key: ${RUSTFS_SECRET_KEY} + s3forcepathstyle: true + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: index_ + period: 24h + +ingester: + chunk_idle_period: 30s + max_chunk_age: 1m + +ruler: + alertmanager_url: http://localhost:9093 +``` + +`s3forcepathstyle: true` und `insecure: true` wählen Path-Style-Adressierung über Plain HTTP, was der Container-Netzwerk-Endpunkt von RustFS erwartet. `chunk_idle_period` und `max_chunk_age` sind verkürzt, damit ein Verifikationslauf nicht die standardmäßigen 30 Minuten auf den Chunk-Flush warten muss. + +Erstellen Sie die Compose-Datei: + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - loki + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - loki + + loki: + image: grafana/loki:latest + command: -config.file=/etc/loki/loki-config.yml + volumes: + - ./loki.yml:/etc/loki/loki-config.yml:ro + ports: + - "3100:3100" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - loki + +networks: + loki: + +volumes: + rustfs-data: +``` + +## 2. Bereitstellung starten + +Prüfen Sie die Compose-Datei, bevor Sie Container starten: + +```bash +docker compose config +``` + +Starten Sie die Dienste und warten Sie, bis die Bucket-Initialisierung abgeschlossen ist: + +```bash +docker compose up -d +docker compose ps -a +``` + +Loki ist bereit, wenn der Readiness-Endpunkt Erfolg meldet: + +```bash +curl -s http://localhost:3100/ready +``` + +```text +ready +``` + +## 3. Log-Streams pushen + +Senden Sie einen Batch von Log-Einträgen über die Push-API: + +```bash +python3 - <<'PY' +import json, time, urllib.request + +values = [] +base_ns = int(time.time() * 1e9) +for i in range(20): + values.append([ + str(base_ns - i * 1_000_000_000), + f"[rustfs-loki-integration] log line {i} stored in RustFS object storage", + ]) + +payload = { + "streams": [{ + "stream": {"job": "rustfs-demo", "service": "loki-integration"}, + "values": values, + }] +} + +req = urllib.request.Request( + "http://localhost:3100/loki/api/v1/push", + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +PY +``` + +```text +push: 204 +``` + +## 4. Logs abfragen + +Fragen Sie den Stream über die Range-Query-API ab: + +```bash +curl -sG "http://localhost:3100/loki/api/v1/query_range" \ + --data-urlencode 'query={job="rustfs-demo"}' \ + --data-urlencode "start=$(($(date +%s) - 3600))000000000" \ + --data-urlencode "end=$(($(date +%s) + 60))000000000" \ + | python3 -m json.tool | head -20 +``` + +Die Antwort enthält die gepushten Zeilen: + +```text +"values": [ + [ + "1789916564000000000", + "[rustfs-loki-integration] log line 0 stored in RustFS object storage" + ], +``` + +## 5. Chunks in RustFS prüfen + +Mit `chunk_idle_period: 30s` überträgt der Ingester den Stream etwa eine Minute nach der letzten Zeile in den Objektspeicher. Listen Sie das Tenant-Präfix über das Bucket-Initialisierungs-Image auf: + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/fake --recursive' +``` + +`fake` ist der Tenant, den Loki verwendet, wenn `auth_enabled` auf `false` steht; jedes Objekt ist ein komprimierter Log-Chunk: + +```text +[2026-09-20 14:48:57] 398 B fake/51610c9b43452db8/1a0bf49f028:1a0bf49f028:f0ed52f7 +[2026-09-20 14:49:33] 670 B fake/cd916b27d004a688/1a0bf4a03ca:1a0bf4a4e03:1376b308 +``` + +Sie können das Präfix auch in der RustFS-Konsole anzeigen: + +![In der RustFS-Konsole gespeicherte Loki-Log-Chunks](./images/rustfs-loki-chunks.png) + +## 6. Stack stoppen oder zurücksetzen + +Stoppen Sie die Container und behalten Sie das RustFS-Datenvolumen: + +```bash +docker compose down +``` + +Um die gespeicherten Logs zu löschen und mit einem leeren RustFS-Volumen zu beginnen, fügen Sie ausdrücklich `--volumes` hinzu: + +```bash +docker compose down --volumes +``` + +## Fehlerbehebung + +### Loki drosselt Schreibvorgänge und meldet "disk usage exceeded threshold" + +Loki überwacht die Festplatte, die sein Write-Ahead-Log hält, und drosselt den Ingester, wenn die Nutzung 90 Prozent überschreitet. Stellen Sie sicher, dass das Volume hinter `path_prefix` genug freien Speicher hat, oder betreiben Sie den Container mit einem tmpfs für das WAL, wenn die Maschine selbst gesund ist. + +### Der Ring meldet Verbindungsfehler zu Port 8500 + +Der Standard-Key-Value-Store für den Ring ist Consul. Für einen Single-Binary setzen Sie `common.ring.kvstore.store: inmemory`, wie in der obigen Konfiguration gezeigt. + +### Push-Anfragen schlagen mit "Ingester is shutting down" fehl + +Der Ingester hat keinen laufenden Zustand erreicht — meist ein übrig gebliebener Container aus einem früheren fehlgeschlagenen Start. Entfernen Sie den Container mit `docker compose down` und starten Sie ihn erneut, oder prüfen Sie die Logs auf den zugrunde liegenden Speicherfehler. + +### AccessDenied- oder 403-Antworten + +Stellen Sie sicher, dass die Anmeldeinformationen in `loki.yml` mit den RustFS-Anmeldeinformationen übereinstimmen und dass der Dienst `create-bucket` erfolgreich abgeschlossen wurde: + +```bash +docker compose logs create-bucket +``` + +## Nächste Schritte + +- Lesen Sie die [S3-Kompatibilitätshinweise](/administration/protocols/s3), bevor Sie weitere S3-Operationen verwenden. +- Erstellen Sie dedizierte Produktions-Anmeldeinformationen mit dem [Access Key Management](/security-compliance/iam/access-token). +- Folgen Sie der [Grafana-Loki-Dokumentation](https://grafana.com/docs/loki/latest/), um Promtail, Alloy oder den OpenTelemetry Collector als Log-Producer anzubinden. diff --git a/content/de/developer/integration/observability/meta.json b/content/de/developer/integration/observability/meta.json index 67036fb9..7bbbe719 100644 --- a/content/de/developer/integration/observability/meta.json +++ b/content/de/developer/integration/observability/meta.json @@ -1,6 +1,8 @@ { "title": "Observability", "pages": [ - "openobserve" + "openobserve", + "loki", + "tempo" ] } diff --git a/content/de/developer/integration/observability/tempo.md b/content/de/developer/integration/observability/tempo.md new file mode 100644 index 00000000..45642d37 --- /dev/null +++ b/content/de/developer/integration/observability/tempo.md @@ -0,0 +1,293 @@ +--- +title: "Tempo" +description: "Betreiben Sie Grafana Tempo mit RustFS als S3-Backend für Trace-Daten, bereitgestellt mit Docker Compose." +--- + +Diese Anleitung betreibt [Grafana Tempo](https://github.com/grafana/tempo) — das verteilte Tracing-Backend von Grafana Labs — mit **RustFS** als Trace-Speicher. Sie starten einen Single-Binary-Tempo mit Docker Compose, pushen einen Trace über den Zipkin-kompatiblen Empfänger, fragen ihn über die Such-API ab und prüfen, dass der Trace-Block als Parquet-Objekt in RustFS gespeichert wird. Der Ablauf wurde mit `grafana/tempo:2.9.5` und `rustfs/rustfs-x86-musl:v2.3.1` verifiziert. + +Sie benötigen Docker mit dem Compose-Plugin. Dieses Setup ist für lokale Integrationstests gedacht, nicht für den Produktivbetrieb. + +## Architektur + +```mermaid +flowchart LR + Client["Instrumented app"] -->|"Zipkin spans"| Tempo["Tempo :3200"] + Tempo -->|"trace blocks (Parquet)"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Tempo nimmt Spans über einen Zipkin-kompatiblen Endpunkt entgegen, puffert sie in einem In-Memory-Block und überträgt abgeschlossene Blöcke als Parquet-Dateien in den Objektspeicher. Suchvorgänge durchsuchen den Block-Index und lesen die Blockdaten aus dem Objektspeicher, sodass jeder Trace einen Tempo-Neustart überlebt. + +## 1. Projektdateien anlegen + +Erstellen Sie ein Arbeitsverzeichnis: + +```bash +mkdir rustfs-tempo +cd rustfs-tempo +``` + +Erstellen Sie eine Umgebungsdatei und ersetzen Sie beide Platzhalter für die Anmeldeinformationen: + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +Verwenden Sie dedizierte Anmeldeinformationen für den Bucket. Committen Sie `.env` nicht in die Versionsverwaltung. + +Erstellen Sie die Tempo-Konfiguration — ein Single-Binary-Setup mit dem S3-Backend, das auf RustFS zeigt, und einer kurzen Blockdauer, damit ein Verifikationslauf nicht die standardmäßigen 30 Minuten warten muss: + +```yaml title="tempo.yml" +server: + http_listen_port: 3200 + +distributor: + receivers: + zipkin: + endpoint: 0.0.0.0:9411 + +ingester: + max_block_duration: 1m + +compactor: + compaction: + block_retention: 24h + +storage: + trace: + backend: s3 + s3: + endpoint: rustfs:9000 + bucket: my-bucket + access_key: + secret_key: + insecure: true + forcepathstyle: true + wal: + path: /var/tempo/wal + blocklist_poll: 30s +``` + +`forcepathstyle: true` und `insecure: true` wählen Path-Style-Adressierung über Plain HTTP, was der Container-Netzwerk-Endpunkt von RustFS erwartet. `max_block_duration: 1m` und `blocklist_poll: 30s` beschleunigen den Flush- und Entdeckungszyklus für Tests. + +Erstellen Sie die Compose-Datei: + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - tempo + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - tempo + + tempo: + image: grafana/tempo:2.9.5 + command: -config.file=/tempo-local.yaml + volumes: + - ./tempo.yml:/tempo-local.yaml:ro + ports: + - "3200:3200" + - "9411:9411" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - tempo + +networks: + tempo: + +volumes: + rustfs-data: +``` + +## 2. Bereitstellung starten + +Prüfen Sie die Compose-Datei, bevor Sie Container starten: + +```bash +docker compose config +``` + +Starten Sie die Dienste und warten Sie, bis die Bucket-Initialisierung abgeschlossen ist: + +```bash +docker compose up -d +docker compose ps -a +``` + +Tempo läuft, wenn der Status-Endpunkt antwortet: + +```bash +curl -s http://localhost:3200/status | head -c 120 +``` + +## 3. Einen Trace pushen + +Posten Sie einen kleinen Zipkin-Trace mit fünf Spans an den Zipkin-kompatiblen Empfänger: + +```bash +python3 - <<'PY' +import json, time, urllib.request, random + +now_us = int(time.time() * 1e6) +trace_id = "".join(random.choice("0123456789abcdef") for _ in range(32)) +span_id = "".join(random.choice("0123456789abcdef") for _ in range(16)) + +spans = [] +for i in range(5): + spans.append({ + "traceId": trace_id, + "id": "".join(random.choice("0123456789abcdef") for _ in range(16)), + "name": f"rustfs-tempo-span-{i}", + "timestamp": now_us - i * 1000, + "duration": 1000 + i * 500, + "localEndpoint": {"serviceName": "rustfs-tempo-demo"}, + "tags": {"job": "rustfs-integration"}, + }) +spans[0]["parent_id"] = "" +for s in spans[1:]: + s["parent_id"] = span_id + +req = urllib.request.Request( + "http://localhost:9411/api/v2/spans", + data=json.dumps(spans).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +print("trace_id:", trace_id) +PY +``` + +```text +push: 202 +``` + +## 4. Den Trace suchen und lesen + +Nach etwa einer Minute überträgt der Ingester den abgeschlossenen Block nach RustFS, und der Compactor entdeckt ihn. Suchen Sie nach einem Tag: + +```bash +curl -s "http://localhost:3200/api/search?tags=job=rustfs-integration" +``` + +```text +{"traces":[{"traceID":"5354809288c0d1a3de0e09ce74d06987","rootServiceName":"rustfs-tempo-demo","rootTraceName":"rustfs-tempo-span-0",...}]} +``` + +Rufen Sie den Trace über seine ID ab — verwenden Sie die Trace-ID aus dem Push-Skript: + +```bash +curl -s "http://localhost:3200/api/traces/" -o /dev/null -w "%{http_code}\n" +``` + +```text +200 +``` + +## 5. Den Trace-Block in RustFS prüfen + +Listen Sie das Tenant-Präfix über das Bucket-Initialisierungs-Image auf: + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/single-tenant --recursive' +``` + +`single-tenant` ist der Tenant, den Tempo verwendet, wenn `multitenancy_enabled` auf `false` steht. Jeder abgeschlossene Trace-Block ist ein Parquet-Objekt: + +```text +[2026-09-20 15:03:54] 25.16 KiB single-tenant/619118dc-a512-4ca6-90f5-e8b15bc9013f/data.parquet +``` + +Sie können das Präfix auch in der RustFS-Konsole anzeigen: + +![Der in der RustFS-Konsole gespeicherte Tempo-Trace-Block](./images/rustfs-tempo-blocks.png) + +Da der Block in RustFS liegt, bleibt der Trace über Tempo-Neustarts hinweg abfragbar — starten Sie den Container neu und wiederholen Sie die Suche zur Bestätigung. + +## 6. Stack stoppen oder zurücksetzen + +Stoppen Sie die Container und behalten Sie das RustFS-Datenvolumen: + +```bash +docker compose down +``` + +Um die gespeicherten Traces zu löschen und mit einem leeren RustFS-Volumen zu beginnen, fügen Sie ausdrücklich `--volumes` hinzu: + +```bash +docker compose down --volumes +``` + +## Fehlerbehebung + +### Die Konfigurationsdatei wird mit "field ingester not found" abgelehnt + +Tempo 3.x hat das Konfigurationslayout geändert. Diese Anleitung pinnt `grafana/tempo:2.9.5`, dessen Konfiguration den oben gezeigten klassischen `ingester`-/`compactor`-Blöcken entspricht. + +### Die Suche liefert direkt nach dem Push keine Traces + +Der Ingester überträgt einen abgeschlossenen Block nach `max_block_duration` (in dieser Anleitung eine Minute), und der Querier entdeckt neue Blöcke bei jedem `blocklist_poll` (30 Sekunden). Warten Sie auf den Flush und suchen Sie erneut, danach prüfen Sie die Tempo-Logs: + +```bash +docker compose logs tempo +``` + +### AccessDenied- oder 403-Antworten + +Stellen Sie sicher, dass die Anmeldeinformationen in `tempo.yml` mit den RustFS-Anmeldeinformationen übereinstimmen und dass der Dienst `create-bucket` erfolgreich abgeschlossen wurde: + +```bash +docker compose logs create-bucket +``` + +### Verbindungs- oder Zertifikatsfehler + +`endpoint` nimmt kein Schema an; `insecure: true` wählt Plain HTTP und `forcepathstyle: true` Path-Style-Adressierung für den Container-Netzwerk-Endpunkt. Verwenden Sie innerhalb des Compose-Netzwerks `rustfs:9000` und auf dem Host `localhost:9000`. + +## Nächste Schritte + +- Lesen Sie die [S3-Kompatibilitätshinweise](/administration/protocols/s3), bevor Sie weitere S3-Operationen verwenden. +- Erstellen Sie dedizierte Produktions-Anmeldeinformationen mit dem [Access Key Management](/security-compliance/iam/access-token). +- Folgen Sie der [Grafana-Tempo-Dokumentation](https://grafana.com/docs/tempo/latest/), um den OpenTelemetry Collector oder instrumentierte Anwendungen als Trace-Producer anzubinden. diff --git a/content/en/developer/integration/observability/images/rustfs-loki-chunks.png b/content/en/developer/integration/observability/images/rustfs-loki-chunks.png new file mode 100644 index 00000000..96f6b9f2 Binary files /dev/null and b/content/en/developer/integration/observability/images/rustfs-loki-chunks.png differ diff --git a/content/en/developer/integration/observability/images/rustfs-tempo-blocks.png b/content/en/developer/integration/observability/images/rustfs-tempo-blocks.png new file mode 100644 index 00000000..d8d3d52e Binary files /dev/null and b/content/en/developer/integration/observability/images/rustfs-tempo-blocks.png differ diff --git a/content/en/developer/integration/observability/index.md b/content/en/developer/integration/observability/index.md index aa8849a8..c91dc8b9 100644 --- a/content/en/developer/integration/observability/index.md +++ b/content/en/developer/integration/observability/index.md @@ -8,5 +8,7 @@ Use **RustFS** as the object storage layer for observability platforms that supp ## Platforms - [OpenObserve](./openobserve.md) +- [Loki](./loki.md) +- [Tempo](./tempo.md) Keep telemetry data in a dedicated bucket, and use credentials scoped to the required bucket operations. diff --git a/content/en/developer/integration/observability/loki.md b/content/en/developer/integration/observability/loki.md new file mode 100644 index 00000000..de3bcbbf --- /dev/null +++ b/content/en/developer/integration/observability/loki.md @@ -0,0 +1,295 @@ +--- +title: "Loki" +description: "Run Grafana Loki with RustFS as its S3 object storage backend, deployed with Docker Compose." +--- + +This guide runs [Grafana Loki](https://github.com/grafana/loki) — the log aggregation system from Grafana Labs — with **RustFS** as its object storage backend. You will start a single-binary Loki with Docker Compose, push log streams through the HTTP API, query them back, and verify that the log chunks are stored as objects in RustFS. The workflow was verified with `grafana/loki:latest` (v3.7.8) and `rustfs/rustfs-x86-musl:v2.3.1`. + +You need Docker with the Compose plugin. This deployment is intended for local integration testing, not production. + +## Architecture + +```mermaid +flowchart LR + Client["Log producer"] -->|"POST /loki/api/v1/push"| Loki["Loki :3100"] + Loki -->|"chunks + index"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Loki ingests log streams into an in-memory chunk and a write-ahead log, flushes compressed chunks to object storage once a stream goes idle, and ships TSDB index files to the same bucket. Queries resolve chunks through the index and read them from object storage. + +## 1. Create the project files + +Create a working directory: + +```bash +mkdir rustfs-loki +cd rustfs-loki +``` + +Create an environment file and replace both credential placeholders: + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +Use dedicated credentials for the bucket. Do not commit `.env` to source control. + +Create the Loki configuration — a single-binary setup with the TSDB schema and the S3 backend pointed at RustFS: + +```yaml title="loki.yml" +auth_enabled: false + +server: + http_listen_port: 3100 + +common: + instance_addr: 127.0.0.1 + path_prefix: /loki + storage: + s3: + endpoint: rustfs:9000 + insecure: true + bucketnames: my-bucket + access_key_id: ${RUSTFS_ACCESS_KEY} + secret_access_key: ${RUSTFS_SECRET_KEY} + s3forcepathstyle: true + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: index_ + period: 24h + +ingester: + chunk_idle_period: 30s + max_chunk_age: 1m + +ruler: + alertmanager_url: http://localhost:9093 +``` + +`s3forcepathstyle: true` and `insecure: true` select path-style addressing over plain HTTP, which is what RustFS expects for the container-network endpoint. `chunk_idle_period` and `max_chunk_age` are lowered so a verification run does not have to wait the default 30 minutes for chunks to flush. + +Create the Compose file: + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - loki + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - loki + + loki: + image: grafana/loki:latest + command: -config.file=/etc/loki/loki-config.yml + volumes: + - ./loki.yml:/etc/loki/loki-config.yml:ro + ports: + - "3100:3100" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - loki + +networks: + loki: + +volumes: + rustfs-data: +``` + +## 2. Start the deployment + +Resolve the Compose file before starting containers: + +```bash +docker compose config +``` + +Start the services and wait for the bucket initializer to finish: + +```bash +docker compose up -d +docker compose ps -a +``` + +Loki is ready when the readiness endpoint reports success: + +```bash +curl -s http://localhost:3100/ready +``` + +```text +ready +``` + +## 3. Push log streams + +Send a batch of log entries through the push API: + +```bash +python3 - <<'PY' +import json, time, urllib.request + +values = [] +base_ns = int(time.time() * 1e9) +for i in range(20): + values.append([ + str(base_ns - i * 1_000_000_000), + f"[rustfs-loki-integration] log line {i} stored in RustFS object storage", + ]) + +payload = { + "streams": [{ + "stream": {"job": "rustfs-demo", "service": "loki-integration"}, + "values": values, + }] +} + +req = urllib.request.Request( + "http://localhost:3100/loki/api/v1/push", + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +PY +``` + +```text +push: 204 +``` + +## 4. Query the logs + +Query the stream back through the range query API: + +```bash +curl -sG "http://localhost:3100/loki/api/v1/query_range" \ + --data-urlencode 'query={job="rustfs-demo"}' \ + --data-urlencode "start=$(($(date +%s) - 3600))000000000" \ + --data-urlencode "end=$(($(date +%s) + 60))000000000" \ + | python3 -m json.tool | head -20 +``` + +The response contains the pushed lines: + +```text +"values": [ + [ + "1789916564000000000", + "[rustfs-loki-integration] log line 0 stored in RustFS object storage" + ], +``` + +## 5. Verify chunks in RustFS + +With `chunk_idle_period: 30s`, the ingester flushes the stream to object storage roughly one minute after the last line. List the tenant prefix through the bucket-initializer image: + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/fake --recursive' +``` + +`fake` is the tenant Loki uses when `auth_enabled` is `false`; each object is one compressed log chunk: + +```text +[2026-09-20 14:48:57] 398 B fake/51610c9b43452db8/1a0bf49f028:1a0bf49f028:f0ed52f7 +[2026-09-20 14:49:33] 670 B fake/cd916b27d004a688/1a0bf4a03ca:1a0bf4a4e03:1376b308 +``` + +You can also browse the prefix in the RustFS Console: + +![Loki log chunks stored in the RustFS Console](./images/rustfs-loki-chunks.png) + +## 6. Stop or reset the stack + +Stop the containers while keeping the RustFS data volume: + +```bash +docker compose down +``` + +To delete the stored logs and start from an empty RustFS volume, explicitly include `--volumes`: + +```bash +docker compose down --volumes +``` + +## Troubleshooting + +### Loki throttles writes and reports "disk usage exceeded threshold" + +Loki monitors the disk that holds its write-ahead log and throttles the ingester when usage passes 90 percent. Make sure the volume behind `path_prefix` has enough free space, or run the container with a tmpfs for the WAL when the machine is otherwise healthy. + +### The ring fails with connection errors to port 8500 + +The default ring key-value store is Consul. For a single binary set `common.ring.kvstore.store: inmemory`, as shown in the configuration above. + +### Push requests fail with "Ingester is shutting down" + +The ingester never reached a running state — usually a leftover container from an earlier failed start. Remove the container with `docker compose down` and start it again, or check the logs for the underlying storage error. + +### AccessDenied or 403 responses + +Confirm the credentials in `loki.yml` match the RustFS credentials and that the `create-bucket` service completed successfully: + +```bash +docker compose logs create-bucket +``` + +## Next steps + +- Review [S3 compatibility notes](/administration/protocols/s3) before adopting additional S3 operations. +- Create dedicated production credentials with [Access Key Management](/security-compliance/iam/access-token). +- Follow the [Grafana Loki documentation](https://grafana.com/docs/loki/latest/) to connect Promtail, Alloy, or the OpenTelemetry Collector as log producers. diff --git a/content/en/developer/integration/observability/meta.json b/content/en/developer/integration/observability/meta.json index 67036fb9..7bbbe719 100644 --- a/content/en/developer/integration/observability/meta.json +++ b/content/en/developer/integration/observability/meta.json @@ -1,6 +1,8 @@ { "title": "Observability", "pages": [ - "openobserve" + "openobserve", + "loki", + "tempo" ] } diff --git a/content/en/developer/integration/observability/tempo.md b/content/en/developer/integration/observability/tempo.md new file mode 100644 index 00000000..3676a392 --- /dev/null +++ b/content/en/developer/integration/observability/tempo.md @@ -0,0 +1,293 @@ +--- +title: "Tempo" +description: "Run Grafana Tempo with RustFS as its S3 trace storage backend, deployed with Docker Compose." +--- + +This guide runs [Grafana Tempo](https://github.com/grafana/tempo) — the distributed tracing backend from Grafana Labs — with **RustFS** as its trace storage. You will start a single-binary Tempo with Docker Compose, push a trace through the Zipkin-compatible receiver, query it through the search API, and verify that the trace block is stored as a Parquet object in RustFS. The workflow was verified with `grafana/tempo:2.9.5` and `rustfs/rustfs-x86-musl:v2.3.1`. + +You need Docker with the Compose plugin. This deployment is intended for local integration testing, not production. + +## Architecture + +```mermaid +flowchart LR + Client["Instrumented app"] -->|"Zipkin spans"| Tempo["Tempo :3200"] + Tempo -->|"trace blocks (Parquet)"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Tempo accepts spans from a Zipkin-compatible endpoint, buffers them in an in-memory block, and flushes completed blocks to object storage as Parquet files. Searches scan the block index and read the block data from object storage, so every trace survives a Tempo restart. + +## 1. Create the project files + +Create a working directory: + +```bash +mkdir rustfs-tempo +cd rustfs-tempo +``` + +Create an environment file and replace both credential placeholders: + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +Use dedicated credentials for the bucket. Do not commit `.env` to source control. + +Create the Tempo configuration — a single-binary setup with the S3 backend pointed at RustFS and a short block duration so a verification run does not have to wait the default 30 minutes: + +```yaml title="tempo.yml" +server: + http_listen_port: 3200 + +distributor: + receivers: + zipkin: + endpoint: 0.0.0.0:9411 + +ingester: + max_block_duration: 1m + +compactor: + compaction: + block_retention: 24h + +storage: + trace: + backend: s3 + s3: + endpoint: rustfs:9000 + bucket: my-bucket + access_key: + secret_key: + insecure: true + forcepathstyle: true + wal: + path: /var/tempo/wal + blocklist_poll: 30s +``` + +`forcepathstyle: true` and `insecure: true` select path-style addressing over plain HTTP, which is what RustFS expects for the container-network endpoint. `max_block_duration: 1m` and `blocklist_poll: 30s` accelerate the flush and discovery cycle for testing. + +Create the Compose file: + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - tempo + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - tempo + + tempo: + image: grafana/tempo:2.9.5 + command: -config.file=/tempo-local.yaml + volumes: + - ./tempo.yml:/tempo-local.yaml:ro + ports: + - "3200:3200" + - "9411:9411" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - tempo + +networks: + tempo: + +volumes: + rustfs-data: +``` + +## 2. Start the deployment + +Resolve the Compose file before starting containers: + +```bash +docker compose config +``` + +Start the services and wait for the bucket initializer to finish: + +```bash +docker compose up -d +docker compose ps -a +``` + +Tempo is up when the status endpoint answers: + +```bash +curl -s http://localhost:3200/status | head -c 120 +``` + +## 3. Push a trace + +Post a small Zipkin trace with five spans to the Zipkin-compatible receiver: + +```bash +python3 - <<'PY' +import json, time, urllib.request, random + +now_us = int(time.time() * 1e6) +trace_id = "".join(random.choice("0123456789abcdef") for _ in range(32)) +span_id = "".join(random.choice("0123456789abcdef") for _ in range(16)) + +spans = [] +for i in range(5): + spans.append({ + "traceId": trace_id, + "id": "".join(random.choice("0123456789abcdef") for _ in range(16)), + "name": f"rustfs-tempo-span-{i}", + "timestamp": now_us - i * 1000, + "duration": 1000 + i * 500, + "localEndpoint": {"serviceName": "rustfs-tempo-demo"}, + "tags": {"job": "rustfs-integration"}, + }) +spans[0]["parent_id"] = "" +for s in spans[1:]: + s["parent_id"] = span_id + +req = urllib.request.Request( + "http://localhost:9411/api/v2/spans", + data=json.dumps(spans).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +print("trace_id:", trace_id) +PY +``` + +```text +push: 202 +``` + +## 4. Search and read the trace + +After roughly one minute the ingester flushes the completed block to RustFS and the compactor discovers it. Search by tag: + +```bash +curl -s "http://localhost:3200/api/search?tags=job=rustfs-integration" +``` + +```text +{"traces":[{"traceID":"5354809288c0d1a3de0e09ce74d06987","rootServiceName":"rustfs-tempo-demo","rootTraceName":"rustfs-tempo-span-0",...}]} +``` + +Fetch the trace by its ID with the trace ID printed by the push script: + +```bash +curl -s "http://localhost:3200/api/traces/" -o /dev/null -w "%{http_code}\n" +``` + +```text +200 +``` + +## 5. Verify the trace block in RustFS + +List the tenant prefix through the bucket-initializer image: + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/single-tenant --recursive' +``` + +`single-tenant` is the tenant Tempo uses when `multitenancy_enabled` is `false`. Each completed trace block is a Parquet object: + +```text +[2026-09-20 15:03:54] 25.16 KiB single-tenant/619118dc-a512-4ca6-90f5-e8b15bc9013f/data.parquet +``` + +You can also browse the prefix in the RustFS Console: + +![The Tempo trace block stored in the RustFS Console](./images/rustfs-tempo-blocks.png) + +Because the block lives in RustFS, the trace stays queryable across Tempo restarts — restart the container and repeat the search to confirm. + +## 6. Stop or reset the stack + +Stop the containers while keeping the RustFS data volume: + +```bash +docker compose down +``` + +To delete the stored traces and start from an empty RustFS volume, explicitly include `--volumes`: + +```bash +docker compose down --volumes +``` + +## Troubleshooting + +### The config file is rejected with "field ingester not found" + +Tempo 3.x changed the configuration layout. This guide pins `grafana/tempo:2.9.5`, whose configuration matches the classic `ingester`/`compactor` blocks shown above. + +### The search returns no traces right after the push + +The ingester flushes a completed block after `max_block_duration` (one minute in this guide), and the querier discovers new blocks on every `blocklist_poll` (30 seconds). Wait for the flush and search again, then check the Tempo logs: + +```bash +docker compose logs tempo +``` + +### AccessDenied or 403 responses + +Confirm the credentials in `tempo.yml` match the RustFS credentials and that the `create-bucket` service completed successfully: + +```bash +docker compose logs create-bucket +``` + +### Connection or certificate errors + +`endpoint` takes no scheme; `insecure: true` selects plain HTTP and `forcepathstyle: true` selects path-style addressing for the container-network endpoint. Inside the Compose network use `rustfs:9000`; from the host use `localhost:9000`. + +## Next steps + +- Review [S3 compatibility notes](/administration/protocols/s3) before adopting additional S3 operations. +- Create dedicated production credentials with [Access Key Management](/security-compliance/iam/access-token). +- Follow the [Grafana Tempo documentation](https://grafana.com/docs/tempo/latest/) to connect the OpenTelemetry Collector or instrumented applications as trace producers. diff --git a/content/fr/developer/integration/observability/images/rustfs-loki-chunks.png b/content/fr/developer/integration/observability/images/rustfs-loki-chunks.png new file mode 100644 index 00000000..96f6b9f2 Binary files /dev/null and b/content/fr/developer/integration/observability/images/rustfs-loki-chunks.png differ diff --git a/content/fr/developer/integration/observability/images/rustfs-tempo-blocks.png b/content/fr/developer/integration/observability/images/rustfs-tempo-blocks.png new file mode 100644 index 00000000..d8d3d52e Binary files /dev/null and b/content/fr/developer/integration/observability/images/rustfs-tempo-blocks.png differ diff --git a/content/fr/developer/integration/observability/index.md b/content/fr/developer/integration/observability/index.md index d40f0c95..cd3d854e 100644 --- a/content/fr/developer/integration/observability/index.md +++ b/content/fr/developer/integration/observability/index.md @@ -8,5 +8,7 @@ Utilisez **RustFS** comme couche de stockage objet pour les plateformes d'observ ## Plateformes - [OpenObserve](./openobserve.md) +- [Loki](./loki.md) +- [Tempo](./tempo.md) Conservez les données de télémétrie dans un bucket dédié et limitez les identifiants aux opérations de bucket requises. diff --git a/content/fr/developer/integration/observability/loki.md b/content/fr/developer/integration/observability/loki.md new file mode 100644 index 00000000..64460376 --- /dev/null +++ b/content/fr/developer/integration/observability/loki.md @@ -0,0 +1,295 @@ +--- +title: "Loki" +description: "Exécutez Grafana Loki avec RustFS comme backend de stockage objet S3, déployé avec Docker Compose." +--- + +Ce guide exécute [Grafana Loki](https://github.com/grafana/loki) — le système d'agrégation de journaux de Grafana Labs — avec **RustFS** comme backend de stockage objet. Vous allez démarrer un Loki single-binary avec Docker Compose, pousser des flux de journaux via l'API HTTP, les interroger, puis vérifier que les chunks de journaux sont stockés comme objets dans RustFS. Le flux a été validé avec `grafana/loki:latest` (v3.7.8) et `rustfs/rustfs-x86-musl:v2.3.1`. + +Vous avez besoin de Docker avec le plugin Compose. Ce déploiement est destiné aux tests d'intégration locaux, pas à la production. + +## Architecture + +```mermaid +flowchart LR + Client["Log producer"] -->|"POST /loki/api/v1/push"| Loki["Loki :3100"] + Loki -->|"chunks + index"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Loki ingère les flux de journaux dans un chunk en mémoire et un journal d'écriture anticipée, transfère les chunks compressés vers le stockage objet dès qu'un flux devient inactif, et envoie les fichiers d'index TSDB vers le même bucket. Les requêtes localisent les chunks via l'index et les lisent depuis le stockage objet. + +## 1. Créer les fichiers du projet + +Créez un répertoire de travail : + +```bash +mkdir rustfs-loki +cd rustfs-loki +``` + +Créez un fichier d'environnement et remplacez les deux espaces réservés d'identifiants : + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +Utilisez des identifiants dédiés pour le bucket. Ne commettez pas `.env` dans le contrôle de version. + +Créez la configuration Loki — un déploiement single-binary avec le schéma TSDB et le backend S3 pointant vers RustFS : + +```yaml title="loki.yml" +auth_enabled: false + +server: + http_listen_port: 3100 + +common: + instance_addr: 127.0.0.1 + path_prefix: /loki + storage: + s3: + endpoint: rustfs:9000 + insecure: true + bucketnames: my-bucket + access_key_id: ${RUSTFS_ACCESS_KEY} + secret_access_key: ${RUSTFS_SECRET_KEY} + s3forcepathstyle: true + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: index_ + period: 24h + +ingester: + chunk_idle_period: 30s + max_chunk_age: 1m + +ruler: + alertmanager_url: http://localhost:9093 +``` + +`s3forcepathstyle: true` et `insecure: true` sélectionnent l'adressage path-style en HTTP simple, attendu par RustFS pour le point de terminaison du réseau de conteneurs. `chunk_idle_period` et `max_chunk_age` sont réduits pour qu'une exécution de vérification n'attende pas les 30 minutes par défaut avant le transfert des chunks. + +Créez le fichier Compose : + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - loki + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - loki + + loki: + image: grafana/loki:latest + command: -config.file=/etc/loki/loki-config.yml + volumes: + - ./loki.yml:/etc/loki/loki-config.yml:ro + ports: + - "3100:3100" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - loki + +networks: + loki: + +volumes: + rustfs-data: +``` + +## 2. Démarrer le déploiement + +Vérifiez le fichier Compose avant de démarrer les conteneurs : + +```bash +docker compose config +``` + +Démarrez les services et attendez la fin de l'initialisation du bucket : + +```bash +docker compose up -d +docker compose ps -a +``` + +Loki est prêt quand le point de terminaison de readiness répond : + +```bash +curl -s http://localhost:3100/ready +``` + +```text +ready +``` + +## 3. Pousser des flux de journaux + +Envoyez un lot d'entrées de journaux via l'API de push : + +```bash +python3 - <<'PY' +import json, time, urllib.request + +values = [] +base_ns = int(time.time() * 1e9) +for i in range(20): + values.append([ + str(base_ns - i * 1_000_000_000), + f"[rustfs-loki-integration] log line {i} stored in RustFS object storage", + ]) + +payload = { + "streams": [{ + "stream": {"job": "rustfs-demo", "service": "loki-integration"}, + "values": values, + }] +} + +req = urllib.request.Request( + "http://localhost:3100/loki/api/v1/push", + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +PY +``` + +```text +push: 204 +``` + +## 4. Interroger les journaux + +Interrogez le flux via l'API de requête par plage : + +```bash +curl -sG "http://localhost:3100/loki/api/v1/query_range" \ + --data-urlencode 'query={job="rustfs-demo"}' \ + --data-urlencode "start=$(($(date +%s) - 3600))000000000" \ + --data-urlencode "end=$(($(date +%s) + 60))000000000" \ + | python3 -m json.tool | head -20 +``` + +La réponse contient les lignes poussées : + +```text +"values": [ + [ + "1789916564000000000", + "[rustfs-loki-integration] log line 0 stored in RustFS object storage" + ], +``` + +## 5. Vérifier les chunks dans RustFS + +Avec `chunk_idle_period: 30s`, l'ingester transfère le flux vers le stockage objet environ une minute après la dernière ligne. Listez le préfixe du tenant via l'image d'initialisation du bucket : + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/fake --recursive' +``` + +`fake` est le tenant utilisé par Loki quand `auth_enabled` vaut `false` ; chaque objet est un chunk de journal compressé : + +```text +[2026-09-20 14:48:57] 398 B fake/51610c9b43452db8/1a0bf49f028:1a0bf49f028:f0ed52f7 +[2026-09-20 14:49:33] 670 B fake/cd916b27d004a688/1a0bf4a03ca:1a0bf4a4e03:1376b308 +``` + +Vous pouvez également parcourir le préfixe dans la console RustFS : + +![Chunks de journaux Loki stockés dans la console RustFS](./images/rustfs-loki-chunks.png) + +## 6. Arrêter ou réinitialiser la pile + +Arrêtez les conteneurs en conservant le volume de données RustFS : + +```bash +docker compose down +``` + +Pour supprimer les journaux stockés et repartir d'un volume RustFS vide, ajoutez explicitement `--volumes` : + +```bash +docker compose down --volumes +``` + +## Dépannage + +### Loki bride les écritures et signale "disk usage exceeded threshold" + +Loki surveille le disque qui héberge son journal d'écriture anticipée et bride l'ingester lorsque l'utilisation dépasse 90 pour cent. Assurez-vous que le volume derrière `path_prefix` dispose de suffisamment d'espace libre, ou exécutez le conteneur avec un tmpfs pour le WAL lorsque la machine est par ailleurs saine. + +### L'anneau signale des erreurs de connexion au port 8500 + +Le magasin clé-valeur par défaut de l'anneau est Consul. Pour un single-binary, définissez `common.ring.kvstore.store: inmemory`, comme montré dans la configuration ci-dessus. + +### Les requêtes de push échouent avec "Ingester is shutting down" + +L'ingester n'a jamais atteint un état en cours d'exécution — généralement un conteneur restant d'un démarrage antérieur en échec. Supprimez le conteneur avec `docker compose down` et redémarrez-le, ou consultez les journaux pour identifier l'erreur de stockage sous-jacente. + +### Réponses AccessDenied ou 403 + +Vérifiez que les identifiants de `loki.yml` correspondent aux identifiants RustFS et que la tâche `create-bucket` s'est terminée avec succès : + +```bash +docker compose logs create-bucket +``` + +## Prochaines étapes + +- Consultez les [notes de compatibilité S3](/administration/protocols/s3) avant d'adopter d'autres opérations S3. +- Créez des identifiants de production dédiés avec la [gestion des clés d'accès](/security-compliance/iam/access-token). +- Suivez la [documentation Grafana Loki](https://grafana.com/docs/loki/latest/) pour connecter Promtail, Alloy ou l'OpenTelemetry Collector comme producteurs de journaux. diff --git a/content/fr/developer/integration/observability/meta.json b/content/fr/developer/integration/observability/meta.json index 40513148..c9fe1865 100644 --- a/content/fr/developer/integration/observability/meta.json +++ b/content/fr/developer/integration/observability/meta.json @@ -1,6 +1,8 @@ { "title": "Observabilité", "pages": [ - "openobserve" + "openobserve", + "loki", + "tempo" ] } diff --git a/content/fr/developer/integration/observability/tempo.md b/content/fr/developer/integration/observability/tempo.md new file mode 100644 index 00000000..a2a33e8a --- /dev/null +++ b/content/fr/developer/integration/observability/tempo.md @@ -0,0 +1,293 @@ +--- +title: "Tempo" +description: "Exécutez Grafana Tempo avec RustFS comme backend de stockage S3 pour les traces, déployé avec Docker Compose." +--- + +Ce guide exécute [Grafana Tempo](https://github.com/grafana/tempo) — le backend de traçage distribué de Grafana Labs — avec **RustFS** comme stockage de traces. Vous allez démarrer un Tempo single-binary avec Docker Compose, pousser une trace via le récepteur compatible Zipkin, l'interroger via l'API de recherche, puis vérifier que le bloc de trace est stocké comme objet Parquet dans RustFS. Le flux a été validé avec `grafana/tempo:2.9.5` et `rustfs/rustfs-x86-musl:v2.3.1`. + +Vous avez besoin de Docker avec le plugin Compose. Ce déploiement est destiné aux tests d'intégration locaux, pas à la production. + +## Architecture + +```mermaid +flowchart LR + Client["Instrumented app"] -->|"Zipkin spans"| Tempo["Tempo :3200"] + Tempo -->|"trace blocks (Parquet)"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Tempo accepte les spans depuis un point de terminaison compatible Zipkin, les met en mémoire tampon dans un bloc, puis transfère les blocs terminés vers le stockage objet sous forme de fichiers Parquet. Les recherches analysent l'index des blocs et lisent les données depuis le stockage objet, de sorte que chaque trace survit à un redémarrage de Tempo. + +## 1. Créer les fichiers du projet + +Créez un répertoire de travail : + +```bash +mkdir rustfs-tempo +cd rustfs-tempo +``` + +Créez un fichier d'environnement et remplacez les deux espaces réservés d'identifiants : + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +Utilisez des identifiants dédiés pour le bucket. Ne commettez pas `.env` dans le contrôle de version. + +Créez la configuration Tempo — un déploiement single-binary avec le backend S3 pointant vers RustFS et une durée de bloc courte pour ne pas attendre les 30 minutes par défaut : + +```yaml title="tempo.yml" +server: + http_listen_port: 3200 + +distributor: + receivers: + zipkin: + endpoint: 0.0.0.0:9411 + +ingester: + max_block_duration: 1m + +compactor: + compaction: + block_retention: 24h + +storage: + trace: + backend: s3 + s3: + endpoint: rustfs:9000 + bucket: my-bucket + access_key: + secret_key: + insecure: true + forcepathstyle: true + wal: + path: /var/tempo/wal + blocklist_poll: 30s +``` + +`forcepathstyle: true` et `insecure: true` sélectionnent l'adressage path-style en HTTP simple, attendu par RustFS pour le point de terminaison du réseau de conteneurs. `max_block_duration: 1m` et `blocklist_poll: 30s` accélèrent le cycle de transfert et de découverte pour les tests. + +Créez le fichier Compose : + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - tempo + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - tempo + + tempo: + image: grafana/tempo:2.9.5 + command: -config.file=/tempo-local.yaml + volumes: + - ./tempo.yml:/tempo-local.yaml:ro + ports: + - "3200:3200" + - "9411:9411" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - tempo + +networks: + tempo: + +volumes: + rustfs-data: +``` + +## 2. Démarrer le déploiement + +Vérifiez le fichier Compose avant de démarrer les conteneurs : + +```bash +docker compose config +``` + +Démarrez les services et attendez la fin de l'initialisation du bucket : + +```bash +docker compose up -d +docker compose ps -a +``` + +Tempo est démarré quand le point de terminaison de statut répond : + +```bash +curl -s http://localhost:3200/status | head -c 120 +``` + +## 3. Pousser une trace + +Publiez une petite trace Zipkin de cinq spans vers le récepteur compatible Zipkin : + +```bash +python3 - <<'PY' +import json, time, urllib.request, random + +now_us = int(time.time() * 1e6) +trace_id = "".join(random.choice("0123456789abcdef") for _ in range(32)) +span_id = "".join(random.choice("0123456789abcdef") for _ in range(16)) + +spans = [] +for i in range(5): + spans.append({ + "traceId": trace_id, + "id": "".join(random.choice("0123456789abcdef") for _ in range(16)), + "name": f"rustfs-tempo-span-{i}", + "timestamp": now_us - i * 1000, + "duration": 1000 + i * 500, + "localEndpoint": {"serviceName": "rustfs-tempo-demo"}, + "tags": {"job": "rustfs-integration"}, + }) +spans[0]["parent_id"] = "" +for s in spans[1:]: + s["parent_id"] = span_id + +req = urllib.request.Request( + "http://localhost:9411/api/v2/spans", + data=json.dumps(spans).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +print("trace_id:", trace_id) +PY +``` + +```text +push: 202 +``` + +## 4. Rechercher et lire la trace + +Après environ une minute, l'ingester transfère le bloc terminé vers RustFS et le compacteur le découvre. Recherchez par tag : + +```bash +curl -s "http://localhost:3200/api/search?tags=job=rustfs-integration" +``` + +```text +{"traces":[{"traceID":"5354809288c0d1a3de0e09ce74d06987","rootServiceName":"rustfs-tempo-demo","rootTraceName":"rustfs-tempo-span-0",...}]} +``` + +Récupérez la trace par son ID avec l'ID affiché par le script de push : + +```bash +curl -s "http://localhost:3200/api/traces/" -o /dev/null -w "%{http_code}\n" +``` + +```text +200 +``` + +## 5. Vérifier le bloc de trace dans RustFS + +Listez le préfixe du tenant via l'image d'initialisation du bucket : + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/single-tenant --recursive' +``` + +`single-tenant` est le tenant utilisé par Tempo quand `multitenancy_enabled` vaut `false`. Chaque bloc de trace terminé est un objet Parquet : + +```text +[2026-09-20 15:03:54] 25.16 KiB single-tenant/619118dc-a512-4ca6-90f5-e8b15bc9013f/data.parquet +``` + +Vous pouvez également parcourir le préfixe dans la console RustFS : + +![Le bloc de trace Tempo stocké dans la console RustFS](./images/rustfs-tempo-blocks.png) + +Le bloc résidant dans RustFS, la trace reste consultable après un redémarrage de Tempo — redémarrez le conteneur et répétez la recherche pour le confirmer. + +## 6. Arrêter ou réinitialiser la pile + +Arrêtez les conteneurs en conservant le volume de données RustFS : + +```bash +docker compose down +``` + +Pour supprimer les traces stockées et repartir d'un volume RustFS vide, ajoutez explicitement `--volumes` : + +```bash +docker compose down --volumes +``` + +## Dépannage + +### Le fichier de configuration est rejeté avec "field ingester not found" + +Tempo 3.x a modifié la structure de configuration. Ce guide épingle `grafana/tempo:2.9.5`, dont la configuration correspond aux blocs classiques `ingester`/`compactor` présentés ci-dessus. + +### La recherche ne renvoie aucune trace juste après le push + +L'ingester transfère un bloc terminé après `max_block_duration` (une minute dans ce guide), et le querier découvre les nouveaux blocs à chaque `blocklist_poll` (30 secondes). Attendez le transfert et recherchez à nouveau, puis consultez les journaux de Tempo : + +```bash +docker compose logs tempo +``` + +### Réponses AccessDenied ou 403 + +Vérifiez que les identifiants de `tempo.yml` correspondent aux identifiants RustFS et que la tâche `create-bucket` s'est terminée avec succès : + +```bash +docker compose logs create-bucket +``` + +### Erreurs de connexion ou de certificat + +`endpoint` ne prend pas de schéma ; `insecure: true` sélectionne HTTP simple et `forcepathstyle: true` l'adressage path-style pour le point de terminaison du réseau de conteneurs. À l'intérieur du réseau Compose, utilisez `rustfs:9000` ; depuis l'hôte, `localhost:9000`. + +## Prochaines étapes + +- Consultez les [notes de compatibilité S3](/administration/protocols/s3) avant d'adopter d'autres opérations S3. +- Créez des identifiants de production dédiés avec la [gestion des clés d'accès](/security-compliance/iam/access-token). +- Suivez la [documentation Grafana Tempo](https://grafana.com/docs/tempo/latest/) pour connecter l'OpenTelemetry Collector ou des applications instrumentées comme producteurs de traces. diff --git a/content/ja/developer/integration/observability/images/rustfs-loki-chunks.png b/content/ja/developer/integration/observability/images/rustfs-loki-chunks.png new file mode 100644 index 00000000..96f6b9f2 Binary files /dev/null and b/content/ja/developer/integration/observability/images/rustfs-loki-chunks.png differ diff --git a/content/ja/developer/integration/observability/images/rustfs-tempo-blocks.png b/content/ja/developer/integration/observability/images/rustfs-tempo-blocks.png new file mode 100644 index 00000000..d8d3d52e Binary files /dev/null and b/content/ja/developer/integration/observability/images/rustfs-tempo-blocks.png differ diff --git a/content/ja/developer/integration/observability/index.md b/content/ja/developer/integration/observability/index.md index 2af33740..896cf131 100644 --- a/content/ja/developer/integration/observability/index.md +++ b/content/ja/developer/integration/observability/index.md @@ -8,5 +8,7 @@ S3 互換エンドポイントをサポートするオブザーバビリティ ## プラットフォーム - [OpenObserve](./openobserve.md) +- [Loki](./loki.md) +- [Tempo](./tempo.md) テレメトリデータは専用バケットに保存し、必要なバケット操作のみに権限が絞られた認証情報を使用してください。 diff --git a/content/ja/developer/integration/observability/loki.md b/content/ja/developer/integration/observability/loki.md new file mode 100644 index 00000000..ad886eb1 --- /dev/null +++ b/content/ja/developer/integration/observability/loki.md @@ -0,0 +1,295 @@ +--- +title: "Loki" +description: "S3 オブジェクトストレージバックエンドとして RustFS を使って Grafana Loki を実行します。Docker Compose でデプロイします。" +--- + +このガイドでは、Grafana Labs のログ集約システムである [Grafana Loki](https://github.com/grafana/loki) を、**RustFS** をオブジェクトストレージバックエンドとして実行します。Docker Compose でシングルバイナリの Loki を起動し、HTTP API 経由でログストリームを push し、クエリで読み戻し、ログチャンクがオブジェクトとして RustFS 内に保存されることを確認します。この流れは `grafana/loki:latest`(v3.7.8)と `rustfs/rustfs-x86-musl:v2.3.1` で検証済みです。 + +Docker と Compose プラグインが必要です。このデプロイはローカルでの統合テストを目的としており、本番環境向けではありません。 + +## アーキテクチャ + +```mermaid +flowchart LR + Client["Log producer"] -->|"POST /loki/api/v1/push"| Loki["Loki :3100"] + Loki -->|"chunks + index"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Loki はログストリームをメモリ内チャンクと先行書き込みログ(WAL)に取り込み、ストリームがアイドルになると圧縮済みチャンクをオブジェクトストレージへフラッシュし、TSDB インデックスファイルを同じバケットへ送出します。クエリ時にはインデックスでチャンクを解決し、オブジェクトストレージから読み込みます。 + +## 1. プロジェクトファイルを作成する + +作業ディレクトリを作成します。 + +```bash +mkdir rustfs-loki +cd rustfs-loki +``` + +環境変数ファイルを作成し、2 つの認証情報プレースホルダーを置き換えます。 + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +バケットには専用の認証情報を使用してください。`.env` をバージョン管理にコミットしないでください。 + +Loki 設定を作成します。TSDB スキーマと、RustFS に向いた S3 バックエンドを使うシングルバイナリ構成です。 + +```yaml title="loki.yml" +auth_enabled: false + +server: + http_listen_port: 3100 + +common: + instance_addr: 127.0.0.1 + path_prefix: /loki + storage: + s3: + endpoint: rustfs:9000 + insecure: true + bucketnames: my-bucket + access_key_id: ${RUSTFS_ACCESS_KEY} + secret_access_key: ${RUSTFS_SECRET_KEY} + s3forcepathstyle: true + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: index_ + period: 24h + +ingester: + chunk_idle_period: 30s + max_chunk_age: 1m + +ruler: + alertmanager_url: http://localhost:9093 +``` + +`s3forcepathstyle: true` と `insecure: true` は、コンテナネットワークのエンドポイントに対して RustFS が期待する、平文 HTTP 上のパススタイルアドレス指定を選択します。`chunk_idle_period` と `max_chunk_age` は短縮しており、検証時にデフォルトの 30 分間チャンクのフラッシュを待つ必要がありません。 + +Compose ファイルを作成します。 + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - loki + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - loki + + loki: + image: grafana/loki:latest + command: -config.file=/etc/loki/loki-config.yml + volumes: + - ./loki.yml:/etc/loki/loki-config.yml:ro + ports: + - "3100:3100" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - loki + +networks: + loki: + +volumes: + rustfs-data: +``` + +## 2. デプロイを起動する + +コンテナを起動する前に Compose ファイルを検証します。 + +```bash +docker compose config +``` + +サービスを起動し、バケット初期化の完了を待ちます。 + +```bash +docker compose up -d +docker compose ps -a +``` + +readiness エンドポイントが成功を返せば Loki の準備ができています。 + +```bash +curl -s http://localhost:3100/ready +``` + +```text +ready +``` + +## 3. ログストリームを push する + +push API にログエントリのバッチを送信します。 + +```bash +python3 - <<'PY' +import json, time, urllib.request + +values = [] +base_ns = int(time.time() * 1e9) +for i in range(20): + values.append([ + str(base_ns - i * 1_000_000_000), + f"[rustfs-loki-integration] log line {i} stored in RustFS object storage", + ]) + +payload = { + "streams": [{ + "stream": {"job": "rustfs-demo", "service": "loki-integration"}, + "values": values, + }] +} + +req = urllib.request.Request( + "http://localhost:3100/loki/api/v1/push", + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +PY +``` + +```text +push: 204 +``` + +## 4. ログを照会する + +レンジクエリ API でストリームを照会します。 + +```bash +curl -sG "http://localhost:3100/loki/api/v1/query_range" \ + --data-urlencode 'query={job="rustfs-demo"}' \ + --data-urlencode "start=$(($(date +%s) - 3600))000000000" \ + --data-urlencode "end=$(($(date +%s) + 60))000000000" \ + | python3 -m json.tool | head -20 +``` + +レスポンスには push した行が含まれます。 + +```text +"values": [ + [ + "1789916564000000000", + "[rustfs-loki-integration] log line 0 stored in RustFS object storage" + ], +``` + +## 5. RustFS 内のチャンクを確認する + +`chunk_idle_period: 30s` の設定では、ingester は最終行から約 1 分後にストリームをオブジェクトストレージへフラッシュします。バケット初期化イメージを使ってテナントのプレフィックスを一覧表示します。 + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/fake --recursive' +``` + +`fake` は `auth_enabled` が `false` の場合に Loki が使うテナントで、各オブジェクトが 1 つの圧縮ログチャンクです。 + +```text +[2026-09-20 14:48:57] 398 B fake/51610c9b43452db8/1a0bf49f028:1a0bf49f028:f0ed52f7 +[2026-09-20 14:49:33] 670 B fake/cd916b27d004a688/1a0bf4a03ca:1a0bf4a4e03:1376b308 +``` + +RustFS コンソールでこのプレフィックスを参照することもできます。 + +![RustFS コンソールに保存された Loki のログチャンク](./images/rustfs-loki-chunks.png) + +## 6. スタックを停止・リセットする + +RustFS データボリュームを保持したままコンテナを停止します。 + +```bash +docker compose down +``` + +保存したログを削除して空の RustFS ボリュームからやり直す場合は、明示的に `--volumes` を付けます。 + +```bash +docker compose down --volumes +``` + +## トラブルシューティング + +### Loki が書き込みをスロットリングし "disk usage exceeded threshold" を報告する + +Loki は WAL を保持するディスクを監視し、使用率が 90% を超えると ingester をスロットリングします。`path_prefix` の背後にあるボリュームに十分な空きがあることを確認してください。マシン自体が健全であれば、このガイドの Compose ファイルのように WAL 用に tmpfs を使う方法もあります。 + +### ring がポート 8500 への接続エラーを報告する + +ring のデフォルト KV ストアは Consul です。シングルバイナリでは、上記の設定どおり `common.ring.kvstore.store: inmemory` を設定してください。 + +### push リクエストが "Ingester is shutting down" で失敗する + +ingester が実行状態に到達していません。多くの場合、以前の失敗した起動で残ったコンテナが原因です。`docker compose down` でコンテナを取り除いて再起動するか、ログで根本のストレージエラーを確認してください。 + +### AccessDenied や 403 レスポンス + +`loki.yml` の認証情報が RustFS の認証情報と一致しているか、`create-bucket` ジョブが正常に完了しているかを確認してください。 + +```bash +docker compose logs create-bucket +``` + +## 次のステップ + +- 追加の S3 オペレーションを採用する前に、[S3 互換性ノート](/administration/protocols/s3)を確認してください。 +- [アクセスキー管理](/security-compliance/iam/access-token)で本番用の専用認証情報を作成してください。 +- [Grafana Loki ドキュメント](https://grafana.com/docs/loki/latest/)に従って、Promtail、Alloy、OpenTelemetry Collector をログプロデューサーとして接続してください。 diff --git a/content/ja/developer/integration/observability/meta.json b/content/ja/developer/integration/observability/meta.json index 06d0def0..f6d8d61c 100644 --- a/content/ja/developer/integration/observability/meta.json +++ b/content/ja/developer/integration/observability/meta.json @@ -1,6 +1,8 @@ { "title": "オブザーバビリティ", "pages": [ - "openobserve" + "openobserve", + "loki", + "tempo" ] } diff --git a/content/ja/developer/integration/observability/tempo.md b/content/ja/developer/integration/observability/tempo.md new file mode 100644 index 00000000..e892431e --- /dev/null +++ b/content/ja/developer/integration/observability/tempo.md @@ -0,0 +1,293 @@ +--- +title: "Tempo" +description: "S3 トレースストレージバックエンドとして RustFS を使って Grafana Tempo を実行します。Docker Compose でデプロイします。" +--- + +このガイドでは、Grafana Labs の分散トレーシングバックエンドである [Grafana Tempo](https://github.com/grafana/tempo) を、**RustFS** をトレースストレージとして実行します。Docker Compose でシングルバイナリの Tempo を起動し、Zipkin 互換レシーバー経由でトレースを push し、検索 API で照会して、トレースブロックが Parquet オブジェクトとして RustFS 内に保存されることを確認します。この流れは `grafana/tempo:2.9.5` と `rustfs/rustfs-x86-musl:v2.3.1` で検証済みです。 + +Docker と Compose プラグインが必要です。このデプロイはローカルでの統合テストを目的としており、本番環境向けではありません。 + +## アーキテクチャ + +```mermaid +flowchart LR + Client["Instrumented app"] -->|"Zipkin spans"| Tempo["Tempo :3200"] + Tempo -->|"trace blocks (Parquet)"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Tempo は Zipkin 互換エンドポイントからスパンを受け付け、メモリ内ブロックにバッファリングし、完了したブロックを Parquet ファイルとしてオブジェクトストレージへフラッシュします。検索はブロックインデックスを走査し、ブロックデータをオブジェクトストレージから読み込むため、すべてのトレースは Tempo の再起動後も保持されます。 + +## 1. プロジェクトファイルを作成する + +作業ディレクトリを作成します。 + +```bash +mkdir rustfs-tempo +cd rustfs-tempo +``` + +環境変数ファイルを作成し、2 つの認証情報プレースホルダーを置き換えます。 + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +バケットには専用の認証情報を使用してください。`.env` をバージョン管理にコミットしないでください。 + +Tempo 設定を作成します。S3 バックエンドが RustFS に向いたシングルバイナリ構成で、検証時にデフォルトの 30 分を待たなくていいようブロック期間を短くしています。 + +```yaml title="tempo.yml" +server: + http_listen_port: 3200 + +distributor: + receivers: + zipkin: + endpoint: 0.0.0.0:9411 + +ingester: + max_block_duration: 1m + +compactor: + compaction: + block_retention: 24h + +storage: + trace: + backend: s3 + s3: + endpoint: rustfs:9000 + bucket: my-bucket + access_key: + secret_key: + insecure: true + forcepathstyle: true + wal: + path: /var/tempo/wal + blocklist_poll: 30s +``` + +`forcepathstyle: true` と `insecure: true` は、コンテナネットワークのエンドポイントに対して RustFS が期待する、平文 HTTP 上のパススタイルアドレス指定を選択します。`max_block_duration: 1m` と `blocklist_poll: 30s` は、テスト向けにフラッシュと発見のサイクルを高速化します。 + +Compose ファイルを作成します。 + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - tempo + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - tempo + + tempo: + image: grafana/tempo:2.9.5 + command: -config.file=/tempo-local.yaml + volumes: + - ./tempo.yml:/tempo-local.yaml:ro + ports: + - "3200:3200" + - "9411:9411" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - tempo + +networks: + tempo: + +volumes: + rustfs-data: +``` + +## 2. デプロイを起動する + +コンテナを起動する前に Compose ファイルを検証します。 + +```bash +docker compose config +``` + +サービスを起動し、バケット初期化の完了を待ちます。 + +```bash +docker compose up -d +docker compose ps -a +``` + +ステータスエンドポイントが応答すれば Tempo が起動しています。 + +```bash +curl -s http://localhost:3200/status | head -c 120 +``` + +## 3. トレースを push する + +5 つのスパンを持つ小さな Zipkin トレースを、Zipkin 互換レシーバーに送信します。 + +```bash +python3 - <<'PY' +import json, time, urllib.request, random + +now_us = int(time.time() * 1e6) +trace_id = "".join(random.choice("0123456789abcdef") for _ in range(32)) +span_id = "".join(random.choice("0123456789abcdef") for _ in range(16)) + +spans = [] +for i in range(5): + spans.append({ + "traceId": trace_id, + "id": "".join(random.choice("0123456789abcdef") for _ in range(16)), + "name": f"rustfs-tempo-span-{i}", + "timestamp": now_us - i * 1000, + "duration": 1000 + i * 500, + "localEndpoint": {"serviceName": "rustfs-tempo-demo"}, + "tags": {"job": "rustfs-integration"}, + }) +spans[0]["parent_id"] = "" +for s in spans[1:]: + s["parent_id"] = span_id + +req = urllib.request.Request( + "http://localhost:9411/api/v2/spans", + data=json.dumps(spans).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +print("trace_id:", trace_id) +PY +``` + +```text +push: 202 +``` + +## 4. トレースを検索して読み込む + +約 1 分後、ingester は完了したブロックを RustFS へフラッシュし、compactor がそれを発見します。タグで検索します。 + +```bash +curl -s "http://localhost:3200/api/search?tags=job=rustfs-integration" +``` + +```text +{"traces":[{"traceID":"5354809288c0d1a3de0e09ce74d06987","rootServiceName":"rustfs-tempo-demo","rootTraceName":"rustfs-tempo-span-0",...}]} +``` + +push スクリプトが表示したトレース ID を使って、ID 指定でトレースを取得します。 + +```bash +curl -s "http://localhost:3200/api/traces/" -o /dev/null -w "%{http_code}\n" +``` + +```text +200 +``` + +## 5. RustFS 内のトレースブロックを確認する + +バケット初期化イメージを使ってテナントのプレフィックスを一覧表示します。 + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/single-tenant --recursive' +``` + +`single-tenant` は `multitenancy_enabled` が `false` の場合に Tempo が使うテナントで、完了した各トレースブロックは 1 つの Parquet オブジェクトです。 + +```text +[2026-09-20 15:03:54] 25.16 KiB single-tenant/619118dc-a512-4ca6-90f5-e8b15bc9013f/data.parquet +``` + +RustFS コンソールでこのプレフィックスを参照することもできます。 + +![RustFS コンソールに保存された Tempo のトレースブロック](./images/rustfs-tempo-blocks.png) + +ブロックは RustFS 内にあるため、Tempo を再起動してもトレースは照会可能です。コンテナを再起動して同じ検索を繰り返せば確認できます。 + +## 6. スタックを停止・リセットする + +RustFS データボリュームを保持したままコンテナを停止します。 + +```bash +docker compose down +``` + +保存したトレースを削除して空の RustFS ボリュームからやり直す場合は、明示的に `--volumes` を付けます。 + +```bash +docker compose down --volumes +``` + +## トラブルシューティング + +### 設定ファイルが "field ingester not found" で拒否される + +Tempo 3.x では設定レイアウトが変更されました。このガイドは `grafana/tempo:2.9.5` に固定しており、上記の古典的な `ingester`/`compactor` ブロックと一致する設定になっています。 + +### push 直後の検索でトレースが見つからない + +ingester は `max_block_duration`(このガイドでは 1 分)経過後に完了したブロックをフラッシュし、querier は毎回 `blocklist_poll`(30 秒)で新しいブロックを発見します。フラッシュを待ってから再度検索し、それでも見つからない場合は Tempo のログを確認してください。 + +```bash +docker compose logs tempo +``` + +### AccessDenied や 403 レスポンス + +`tempo.yml` の認証情報が RustFS の認証情報と一致しているか、`create-bucket` ジョブが正常に完了しているかを確認してください。 + +```bash +docker compose logs create-bucket +``` + +### 接続エラーや証明書エラー + +`endpoint` にスキーマは指定しません。`insecure: true` が平文 HTTP を、`forcepathstyle: true` がコンテナネットワークエンドポイント向けのパススタイルアドレス指定を選択します。Compose ネットワーク内では `rustfs:9000` を、ホストからは `localhost:9000` を使用してください。 + +## 次のステップ + +- 追加の S3 オペレーションを採用する前に、[S3 互換性ノート](/administration/protocols/s3)を確認してください。 +- [アクセスキー管理](/security-compliance/iam/access-token)で本番用の専用認証情報を作成してください。 +- [Grafana Tempo ドキュメント](https://grafana.com/docs/tempo/latest/)に従って、OpenTelemetry Collector や計装済みアプリケーションをトレースプロデューサーとして接続してください。 diff --git a/content/zh/developer/integration/observability/images/rustfs-loki-chunks.png b/content/zh/developer/integration/observability/images/rustfs-loki-chunks.png new file mode 100644 index 00000000..b58446c1 Binary files /dev/null and b/content/zh/developer/integration/observability/images/rustfs-loki-chunks.png differ diff --git a/content/zh/developer/integration/observability/images/rustfs-tempo-blocks.png b/content/zh/developer/integration/observability/images/rustfs-tempo-blocks.png new file mode 100644 index 00000000..42e2b7eb Binary files /dev/null and b/content/zh/developer/integration/observability/images/rustfs-tempo-blocks.png differ diff --git a/content/zh/developer/integration/observability/index.md b/content/zh/developer/integration/observability/index.md index b64f76d5..9234d46d 100644 --- a/content/zh/developer/integration/observability/index.md +++ b/content/zh/developer/integration/observability/index.md @@ -8,5 +8,7 @@ description: "通过 S3 兼容对象存储接口,将可观测性平台连接 ## 平台 - [OpenObserve](./openobserve.md) +- [Loki](./loki.md) +- [Tempo](./tempo.md) 请使用专用的存储桶保存遥测数据,并为凭证仅授予所需桶操作的权限。 diff --git a/content/zh/developer/integration/observability/loki.md b/content/zh/developer/integration/observability/loki.md new file mode 100644 index 00000000..9366924a --- /dev/null +++ b/content/zh/developer/integration/observability/loki.md @@ -0,0 +1,295 @@ +--- +title: "Loki" +description: "使用 Docker Compose 运行 Grafana Loki,以 RustFS 作为其 S3 对象存储后端。" +--- + +本指南运行 [Grafana Loki](https://github.com/grafana/loki)——Grafana Labs 的日志聚合系统——并以 **RustFS** 作为其对象存储后端。你将使用 Docker Compose 启动单节点 Loki,通过 HTTP API 推送日志流,查询它们,并确认日志 chunk 以对象形式存储在 RustFS 中。整个流程使用 `grafana/loki:latest`(v3.7.8)和 `rustfs/rustfs-x86-musl:v2.3.1` 验证通过。 + +你需要安装带有 Compose 插件的 Docker。本部署用于本地集成测试,不适用于生产环境。 + +## 架构 + +```mermaid +flowchart LR + Client["Log producer"] -->|"POST /loki/api/v1/push"| Loki["Loki :3100"] + Loki -->|"chunks + index"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Loki 将日志流摄入内存 chunk 和预写日志(WAL),在流空闲后把压缩后的 chunk 刷写到对象存储,并把 TSDB 索引文件上传到同一个桶。查询时通过索引定位 chunk 并从对象存储读取。 + +## 1. 创建项目文件 + +创建工作目录: + +```bash +mkdir rustfs-loki +cd rustfs-loki +``` + +创建环境变量文件,并替换两个凭证占位符: + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +请为桶使用专用的凭证,不要将 `.env` 提交到版本控制。 + +创建 Loki 配置——采用 TSDB 模式、S3 后端指向 RustFS 的单节点配置: + +```yaml title="loki.yml" +auth_enabled: false + +server: + http_listen_port: 3100 + +common: + instance_addr: 127.0.0.1 + path_prefix: /loki + storage: + s3: + endpoint: rustfs:9000 + insecure: true + bucketnames: my-bucket + access_key_id: ${RUSTFS_ACCESS_KEY} + secret_access_key: ${RUSTFS_SECRET_KEY} + s3forcepathstyle: true + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-10-24 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: index_ + period: 24h + +ingester: + chunk_idle_period: 30s + max_chunk_age: 1m + +ruler: + alertmanager_url: http://localhost:9093 +``` + +`s3forcepathstyle: true` 和 `insecure: true` 表示对容器网络端点使用纯 HTTP 上的 path-style 寻址,这正是 RustFS 所期望的。`chunk_idle_period` 和 `max_chunk_age` 被调小了,这样验证时不必等待默认的 30 分钟才会刷写 chunk。 + +创建 Compose 文件: + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - loki + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - loki + + loki: + image: grafana/loki:latest + command: -config.file=/etc/loki/loki-config.yml + volumes: + - ./loki.yml:/etc/loki/loki-config.yml:ro + ports: + - "3100:3100" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - loki + +networks: + loki: + +volumes: + rustfs-data: +``` + +## 2. 启动部署 + +启动容器前先解析 Compose 文件: + +```bash +docker compose config +``` + +启动服务并等待桶初始化任务完成: + +```bash +docker compose up -d +docker compose ps -a +``` + +就绪端点返回成功即表示 Loki 已就绪: + +```bash +curl -s http://localhost:3100/ready +``` + +```text +ready +``` + +## 3. 推送日志流 + +通过推送 API 发送一批日志条目: + +```bash +python3 - <<'PY' +import json, time, urllib.request + +values = [] +base_ns = int(time.time() * 1e9) +for i in range(20): + values.append([ + str(base_ns - i * 1_000_000_000), + f"[rustfs-loki-integration] log line {i} stored in RustFS object storage", + ]) + +payload = { + "streams": [{ + "stream": {"job": "rustfs-demo", "service": "loki-integration"}, + "values": values, + }] +} + +req = urllib.request.Request( + "http://localhost:3100/loki/api/v1/push", + data=json.dumps(payload).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +PY +``` + +```text +push: 204 +``` + +## 4. 查询日志 + +通过范围查询 API 把日志查询回来: + +```bash +curl -sG "http://localhost:3100/loki/api/v1/query_range" \ + --data-urlencode 'query={job="rustfs-demo"}' \ + --data-urlencode "start=$(($(date +%s) - 3600))000000000" \ + --data-urlencode "end=$(($(date +%s) + 60))000000000" \ + | python3 -m json.tool | head -20 +``` + +响应中包含推送的日志行: + +```text +"values": [ + [ + "1789916564000000000", + "[rustfs-loki-integration] log line 0 stored in RustFS object storage" + ], +``` + +## 5. 在 RustFS 中验证 chunk + +设置了 `chunk_idle_period: 30s` 后,ingester 会在最后一行日志大约一分钟后把流刷写到对象存储。通过桶初始化镜像列出租户前缀: + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/fake --recursive' +``` + +`fake` 是 `auth_enabled` 为 `false` 时 Loki 使用的租户,每个对象即一个压缩后的日志 chunk: + +```text +[2026-09-20 14:48:57] 398 B fake/51610c9b43452db8/1a0bf49f028:1a0bf49f028:f0ed52f7 +[2026-09-20 14:49:33] 670 B fake/cd916b27d004a688/1a0bf4a03ca:1a0bf4a4e03:1376b308 +``` + +你也可以在 RustFS 控制台中浏览该前缀: + +![RustFS 控制台中存储的 Loki 日志 chunk](./images/rustfs-loki-chunks.png) + +## 6. 停止或重置环境 + +停止容器并保留 RustFS 数据卷: + +```bash +docker compose down +``` + +如需删除已存储的日志并从空的 RustFS 数据卷开始,请显式加上 `--volumes`: + +```bash +docker compose down --volumes +``` + +## 故障排除 + +### Loki 节流写入并提示 "disk usage exceeded threshold" + +Loki 会监控预写日志所在磁盘,使用率超过 90% 时会对 ingester 节流。请确保 `path_prefix` 所在卷有足够的可用空间;如果机器本身健康,也可以像本指南的 Compose 文件那样为 WAL 使用 tmpfs。 + +### ring 报连接 8500 端口的错误 + +ring 的默认键值存储是 Consul。单节点部署请按上文配置设置 `common.ring.kvstore.store: inmemory`。 + +### 推送请求报 "Ingester is shutting down" + +ingester 未能进入运行状态——通常是之前一次启动失败留下的容器。先用 `docker compose down` 移除容器再重新启动,或查看日志寻找底层存储错误。 + +### 返回 AccessDenied 或 403 响应 + +确认 `loki.yml` 中的凭证与 RustFS 凭证一致,并确认 `create-bucket` 任务已成功完成: + +```bash +docker compose logs create-bucket +``` + +## 后续步骤 + +- 在采用其他 S3 操作前,请查看 [S3 兼容性说明](/administration/protocols/s3)。 +- 通过[访问密钥管理](/security-compliance/iam/access-token)创建专用的生产凭证。 +- 按照 [Grafana Loki 文档](https://grafana.com/docs/loki/latest/)接入 Promtail、Alloy 或 OpenTelemetry Collector 作为日志生产者。 diff --git a/content/zh/developer/integration/observability/meta.json b/content/zh/developer/integration/observability/meta.json index 3828ee3d..7efedc39 100644 --- a/content/zh/developer/integration/observability/meta.json +++ b/content/zh/developer/integration/observability/meta.json @@ -1,6 +1,8 @@ { "title": "可观测性", "pages": [ - "openobserve" + "openobserve", + "loki", + "tempo" ] } diff --git a/content/zh/developer/integration/observability/tempo.md b/content/zh/developer/integration/observability/tempo.md new file mode 100644 index 00000000..d3107174 --- /dev/null +++ b/content/zh/developer/integration/observability/tempo.md @@ -0,0 +1,293 @@ +--- +title: "Tempo" +description: "使用 Docker Compose 运行 Grafana Tempo,以 RustFS 作为其 S3 追踪数据存储后端。" +--- + +本指南运行 [Grafana Tempo](https://github.com/grafana/tempo)——Grafana Labs 的分布式追踪后端——并以 **RustFS** 作为其追踪数据存储。你将使用 Docker Compose 启动单节点 Tempo,通过 Zipkin 兼容接收器推送一条追踪,通过搜索 API 查询它,并确认追踪 block 以 Parquet 对象的形式存储在 RustFS 中。整个流程使用 `grafana/tempo:2.9.5` 和 `rustfs/rustfs-x86-musl:v2.3.1` 验证通过。 + +你需要安装带有 Compose 插件的 Docker。本部署用于本地集成测试,不适用于生产环境。 + +## 架构 + +```mermaid +flowchart LR + Client["Instrumented app"] -->|"Zipkin spans"| Tempo["Tempo :3200"] + Tempo -->|"trace blocks (Parquet)"| RustFS["RustFS :9000"] + Init["init-bucket job"] -->|"create my-bucket"| RustFS +``` + +Tempo 从 Zipkin 兼容端点接收 span,先缓冲到内存 block 中,再把完成的 block 以 Parquet 文件刷写到对象存储。搜索会扫描 block 索引并从对象存储读取 block 数据,因此每条追踪都能在 Tempo 重启后保留。 + +## 1. 创建项目文件 + +创建工作目录: + +```bash +mkdir rustfs-tempo +cd rustfs-tempo +``` + +创建环境变量文件,并替换两个凭证占位符: + +```ini title=".env" +RUSTFS_ACCESS_KEY= +RUSTFS_SECRET_KEY= +``` + +请为桶使用专用的凭证,不要将 `.env` 提交到版本控制。 + +创建 Tempo 配置——S3 后端指向 RustFS 的单节点配置,并把 block 时长调短以便验证时无需等待默认的 30 分钟: + +```yaml title="tempo.yml" +server: + http_listen_port: 3200 + +distributor: + receivers: + zipkin: + endpoint: 0.0.0.0:9411 + +ingester: + max_block_duration: 1m + +compactor: + compaction: + block_retention: 24h + +storage: + trace: + backend: s3 + s3: + endpoint: rustfs:9000 + bucket: my-bucket + access_key: + secret_key: + insecure: true + forcepathstyle: true + wal: + path: /var/tempo/wal + blocklist_poll: 30s +``` + +`forcepathstyle: true` 和 `insecure: true` 表示对容器网络端点使用纯 HTTP 上的 path-style 寻址,这正是 RustFS 所期望的。`max_block_duration: 1m` 和 `blocklist_poll: 30s` 用于加速验证时的刷写与发现周期。 + +创建 Compose 文件: + +```yaml title="compose.yaml" +services: + rustfs: + image: rustfs/rustfs-x86-musl:v2.3.1 + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + RUSTFS_VOLUMES: /data + RUSTFS_ADDRESS: ":9000" + RUSTFS_CONSOLE_ADDRESS: ":9001" + RUSTFS_CONSOLE_ENABLE: "true" + volumes: + - rustfs-data:/data + ports: + - "9000:9000" + - "9001:9001" + healthcheck: + test: ["CMD", "curl", "-sf", "http://127.0.0.1:9000/health"] + interval: 10s + timeout: 5s + retries: 6 + start_period: 10s + networks: + - tempo + + create-bucket: + image: rustfs/rc:latest + depends_on: + rustfs: + condition: service_healthy + environment: + RUSTFS_ACCESS_KEY: ${RUSTFS_ACCESS_KEY} + RUSTFS_SECRET_KEY: ${RUSTFS_SECRET_KEY} + entrypoint: + - /bin/sh + - -c + - | + until /usr/bin/rc alias set rustfs http://rustfs:9000 "$${RUSTFS_ACCESS_KEY}" "$${RUSTFS_SECRET_KEY}"; do + echo "Waiting for RustFS..." + sleep 2 + done + /usr/bin/rc ls rustfs/my-bucket >/dev/null 2>&1 || /usr/bin/rc mb rustfs/my-bucket + networks: + - tempo + + tempo: + image: grafana/tempo:2.9.5 + command: -config.file=/tempo-local.yaml + volumes: + - ./tempo.yml:/tempo-local.yaml:ro + ports: + - "3200:3200" + - "9411:9411" + depends_on: + create-bucket: + condition: service_completed_successfully + networks: + - tempo + +networks: + tempo: + +volumes: + rustfs-data: +``` + +## 2. 启动部署 + +启动容器前先解析 Compose 文件: + +```bash +docker compose config +``` + +启动服务并等待桶初始化任务完成: + +```bash +docker compose up -d +docker compose ps -a +``` + +状态端点有响应即表示 Tempo 已启动: + +```bash +curl -s http://localhost:3200/status | head -c 120 +``` + +## 3. 推送一条追踪 + +向 Zipkin 兼容接收器提交一条包含五个 span 的 Zipkin 追踪: + +```bash +python3 - <<'PY' +import json, time, urllib.request, random + +now_us = int(time.time() * 1e6) +trace_id = "".join(random.choice("0123456789abcdef") for _ in range(32)) +span_id = "".join(random.choice("0123456789abcdef") for _ in range(16)) + +spans = [] +for i in range(5): + spans.append({ + "traceId": trace_id, + "id": "".join(random.choice("0123456789abcdef") for _ in range(16)), + "name": f"rustfs-tempo-span-{i}", + "timestamp": now_us - i * 1000, + "duration": 1000 + i * 500, + "localEndpoint": {"serviceName": "rustfs-tempo-demo"}, + "tags": {"job": "rustfs-integration"}, + }) +spans[0]["parent_id"] = "" +for s in spans[1:]: + s["parent_id"] = span_id + +req = urllib.request.Request( + "http://localhost:9411/api/v2/spans", + data=json.dumps(spans).encode(), + headers={"Content-Type": "application/json"}, + method="POST", +) +with urllib.request.urlopen(req, timeout=30) as r: + print("push:", r.status) +print("trace_id:", trace_id) +PY +``` + +```text +push: 202 +``` + +## 4. 搜索并读取追踪 + +大约一分钟后,ingester 会把完成的 block 刷写到 RustFS,compactor 也会发现它。按标签搜索: + +```bash +curl -s "http://localhost:3200/api/search?tags=job=rustfs-integration" +``` + +```text +{"traces":[{"traceID":"5354809288c0d1a3de0e09ce74d06987","rootServiceName":"rustfs-tempo-demo","rootTraceName":"rustfs-tempo-span-0",...}]} +``` + +用推送脚本打印的追踪 ID 按 ID 获取追踪: + +```bash +curl -s "http://localhost:3200/api/traces/" -o /dev/null -w "%{http_code}\n" +``` + +```text +200 +``` + +## 5. 在 RustFS 中验证追踪 block + +通过桶初始化镜像列出租户前缀: + +```bash +docker compose run --rm --entrypoint /bin/sh create-bucket -c \ + '/usr/bin/rc alias set rustfs http://rustfs:9000 "$RUSTFS_ACCESS_KEY" "$RUSTFS_SECRET_KEY" >/dev/null && /usr/bin/rc ls rustfs/my-bucket/single-tenant --recursive' +``` + +`single-tenant` 是 `multitenancy_enabled` 为 `false` 时 Tempo 使用的租户,每个完成的追踪 block 都是一个 Parquet 对象: + +```text +[2026-09-20 15:03:54] 25.16 KiB single-tenant/619118dc-a512-4ca6-90f5-e8b15bc9013f/data.parquet +``` + +你也可以在 RustFS 控制台中浏览该前缀: + +![RustFS 控制台中存储的 Tempo 追踪 block](./images/rustfs-tempo-blocks.png) + +由于 block 存放在 RustFS 中,Tempo 重启后追踪依然可查——重启容器并重复上面的搜索即可确认。 + +## 6. 停止或重置环境 + +停止容器并保留 RustFS 数据卷: + +```bash +docker compose down +``` + +如需删除已存储的追踪并从空的 RustFS 数据卷开始,请显式加上 `--volumes`: + +```bash +docker compose down --volumes +``` + +## 故障排除 + +### 配置文件被拒绝并提示 "field ingester not found" + +Tempo 3.x 更改了配置结构。本指南锁定 `grafana/tempo:2.9.5`,其配置与上文展示的经典 `ingester`/`compactor` 块一致。 + +### 推送后立即搜索查不到追踪 + +ingester 会在 `max_block_duration`(本指南为一分钟)之后刷写完成的 block,查询端则按 `blocklist_poll`(30 秒)发现新 block。请等待刷写后再次搜索,并检查 Tempo 日志: + +```bash +docker compose logs tempo +``` + +### 返回 AccessDenied 或 403 响应 + +确认 `tempo.yml` 中的凭证与 RustFS 凭证一致,并确认 `create-bucket` 任务已成功完成: + +```bash +docker compose logs create-bucket +``` + +### 连接或证书错误 + +`endpoint` 不带协议;`insecure: true` 表示纯 HTTP,`forcepathstyle: true` 表示容器网络端点的 path-style 寻址。Compose 网络内使用 `rustfs:9000`,宿主机上使用 `localhost:9000`。 + +## 后续步骤 + +- 在采用其他 S3 操作前,请查看 [S3 兼容性说明](/administration/protocols/s3)。 +- 通过[访问密钥管理](/security-compliance/iam/access-token)创建专用的生产凭证。 +- 按照 [Grafana Tempo 文档](https://grafana.com/docs/tempo/latest/)接入 OpenTelemetry Collector 或已插桩的应用作为追踪生产者。