惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Security Blog
Microsoft Security Blog
S
Secure Thoughts
酷 壳 – CoolShell
酷 壳 – CoolShell
S
SegmentFault 最新的问题
WordPress大学
WordPress大学
Hugging Face - Blog
Hugging Face - Blog
人人都是产品经理
人人都是产品经理
美团技术团队
博客园 - 三生石上(FineUI控件)
Jina AI
Jina AI
V
Visual Studio Blog
腾讯CDC
小众软件
小众软件
有赞技术团队
有赞技术团队
博客园 - 聂微东
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
IT之家
IT之家
C
CERT Recently Published Vulnerability Notes
大猫的无限游戏
大猫的无限游戏
T
Threat Research - Cisco Blogs
SecWiki News
SecWiki News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Hacker News
The Hacker News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Troy Hunt's Blog
月光博客
月光博客
雷峰网
雷峰网
T
Tor Project blog
I
Intezer
S
Securelist
罗磊的独立博客
The Register - Security
The Register - Security
H
Hackread – Cybersecurity News, Data Breaches, AI and More
NISL@THU
NISL@THU
Google Online Security Blog
Google Online Security Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Cisco Talos Blog
Cisco Talos Blog
云风的 BLOG
云风的 BLOG
www.infosecurity-magazine.com
www.infosecurity-magazine.com
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
The Cloudflare Blog
Attack and Defense Labs
Attack and Defense Labs
P
Privacy International News Feed
W
WeLiveSecurity
Forbes - Security
Forbes - Security
量子位
Last Week in AI
Last Week in AI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Loki Multi-Cluster Log Aggregation Across AWS Regions
Oleksandr Kuryzhev · 2026-06-19 · via DEV Community

Oleksandr Kuryzhev

Originally published on kuryzhev.cloud


You set up Loki in every region, pointed Grafana at each datasource, and called it done — but the moment a cross-region incident hit, you had no way to correlate logs without switching datasources mid-investigation. That context-switching costs minutes during an outage, and minutes matter. Loki multi-cluster log aggregation across AWS regions solves this by centralizing ingestion while keeping agents close to the workloads that generate logs.

This post covers the internals of how Loki actually handles cross-region log federation, the three architectural mistakes I see most often, and the production topology we use today with Loki 3.0.x on EKS.

What Loki Multi-Cluster Aggregation Actually Does

The first thing to understand is that Loki is not Prometheus. There is no federation endpoint, no remote read, no hierarchical scrape chain. Loki operates on a push model — agents (Promtail 2.9.x or Grafana Alloy 1.2+) ship log streams directly to a Loki write path. When engineers first hear "multi-cluster Loki," they assume something analogous to Prometheus federation, where a central instance scrapes regional ones. That assumption gets you burned fast.

What actually happens in a correctly designed setup: agents in eu-west-1 and ap-southeast-1 push log streams to a single Loki deployment in a hub region (us-east-1). Every log line arrives tagged with cluster and region labels. These labels become your primary index dimensions alongside the standard Kubernetes metadata. A Querier in us-east-1 can answer a LogQL query like {cluster="prod-eu-west-1"} |= "ERROR" because the underlying chunks are in a shared S3 bucket — it doesn't matter where the log originated geographically.

Understanding the component roles matters here. The Distributor receives incoming log streams and validates label cardinality before routing to Ingesters. The Ingester buffers recent logs in memory (up to chunk_idle_period: 5m in our config — more on why we reduced this from the 30m default shortly). The Querier reads from both in-memory Ingester data and flushed S3 chunks, merging results transparently. None of these components need to be co-located with the agents. The separation is what makes the hub-and-spoke topology work.

One subtle point: if you use a single S3 bucket in us-east-1, agents in eu-west-1 pay cross-region data transfer costs on every log push. We address this with VPC peering and private NLB endpoints. The logs still land in one bucket, but the network path stays off the public internet.

How People Set This Up Wrong

I've reviewed a lot of Loki deployments. The same three mistakes appear repeatedly, and each one looks fine until it doesn't.

Mistake 1: Multiple Loki stacks, multiple Grafana datasources. This is the path of least resistance and it works — right up until you need to answer "did the error in eu-west-1 correlate with the deployment in us-east-1?" LogQL has no cross-datasource join. You're manually copying timestamps between two browser tabs. I stopped recommending this topology after we spent 40 minutes during an incident reconciling two separate Explore sessions. One central Loki eliminates the problem entirely.

Mistake 2: The filesystem storage backend that quietly becomes production. Someone spins up a quick Loki instance with storage.type: filesystem for "just testing." Three months later it's ingesting 20GB/day and nobody has touched the config. When the Ingester pod reschedules to a different AZ — and it will — the chunks on the old node's local disk are gone. No error in Grafana. No alert fires. Log lines just disappear. The default chunk_idle_period: 30m means you can lose up to 30 minutes of logs silently. We reduce this to 5m and use S3 exclusively.

Mistake 3: No stream selector discipline from day one. Default Promtail configs targeting /var/log/**/*.log without excluding /var/log/containers symlinks cause duplicate ingestion immediately. But the slower-burning problem is label cardinality explosion. Teams start adding pod_name, image_tag, commit_sha as Loki labels because it feels natural coming from Prometheus. Within 48 hours of a busy cluster, the number of unique label combinations saturates the index. Loki starts returning too many outstanding requests — which is not a network error, it maps to querier.max_outstanding_requests_per_tenant (default 100). High-cardinality values belong in log content, parsed with | json or | logfmt, not in the label index.

Watch out: setting auth_enabled: false in any setup you might scale later is a trap. All logs land in the fake tenant. Per-tenant retention overrides never apply. You cannot isolate prod logs from dev logs. Enabling auth later requires re-ingesting or manually migrating tenant assignments — neither is fun.

The Correct Approach — Centralized Loki with Regional Agents

The topology we run: one Loki deployment in microservices mode on EKS in us-east-1, Grafana Alloy agents in each spoke cluster, all writing to a shared S3 bucket. Monolithic mode is fine up to roughly 30-50GB/day ingestion; past that, memory limits on a single pod become the bottleneck and you want separate scaling for Distributors, Ingesters, and Queriers.

The Helm values below configure Loki in microservices mode. Pay attention to the comments — several of these settings have non-obvious failure modes.

# loki-values.yaml — Helm values for Loki microservices mode, hub region (us-east-1)
# Chart: grafana/loki v6.x, Loki image: grafana/loki:3.0.0

loki:
  auth_enabled: true  # REQUIRED for multi-tenant; false puts everything in 'fake' tenant

  commonConfig:
    replication_factor: 3  # Must match ingester replica count below

  storage:
    type: s3
    bucketNames:
      chunks: loki-chunks-prod-us-east-1
      ruler: loki-ruler-prod-us-east-1
      admin: loki-admin-prod-us-east-1
    s3:
      region: us-east-1
      # Use IRSA — no static credentials here
      insecure: false

  schemaConfig:
    configs:
      - from: "2024-01-01"
        store: tsdb          # TSDB index, not boltdb-shipper (deprecated)
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h

  limits_config:
    ingestion_rate_mb: 20          # Per-tenant; raise for high-volume clusters
    ingestion_burst_size_mb: 40
    max_label_names_per_series: 20 # Default 15 is too low for k8s workloads
    max_query_parallelism: 32      # Enables query frontend sharding
    retention_period: 720h         # 30d default; override per tenant in overrides

  ingester:
    chunk_idle_period: 5m          # Flush sooner; default 30m risks data loss
    chunk_target_size: 3145728     # 3MB chunks; reduces S3 PUT count vs default 1.5MB
    chunk_encoding: snappy         # Better compression ratio than gzip for logs

  compactor:
    working_directory: /var/loki/compactor
    retention_enabled: true        # Must be explicit; does NOT default to true
    delete_request_store: s3

  query_frontend:
    split_queries_by_interval: 15m # Parallelizes long time-range queries

# Component replica counts
ingester:
  replicas: 3          # Must match replication_factor above

querier:
  replicas: 3
  maxUnavailable: 1

queryFrontend:
  replicas: 2

compactor:
  replicas: 1          # CRITICAL: never run more than 1; no distributed locking

distributor:
  replicas: 2

# IRSA annotation — replace with your actual role ARN
serviceAccount:
  annotations:
    eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/loki-s3-irsa-role"

The Grafana Alloy config below runs in each spoke cluster. The key discipline is injecting cluster and region labels at the agent level, not via Loki's relabeling. If you try to add these labels server-side, the index is already written without them and your queries break in confusing ways.

# alloy-config.river — Grafana Alloy v1.2+ agent config for spoke cluster (eu-west-1)
# Runs in each non-hub EKS cluster; ships logs to central Loki in us-east-1

// Discover all pods in the cluster
discovery.kubernetes "pods" {
  role = "pod"
}

// Relabel: keep only necessary labels, inject cluster identity
discovery.relabel "pod_logs" {
  targets = discovery.kubernetes.pods.targets

  // Inject cluster and region as static labels — set here, not at Loki level
  rule {
    target_label = "cluster"
    replacement  = "prod-eu-west-1"   // Hardcode per cluster; do not use env var
  }
  rule {
    target_label = "region"
    replacement  = "eu-west-1"
  }

  // Keep namespace, pod, container for filtering
  rule {
    source_labels = ["__meta_kubernetes_namespace"]
    target_label  = "namespace"
  }
  rule {
    source_labels = ["__meta_kubernetes_pod_name"]
    target_label  = "pod"
  }
  rule {
    source_labels = ["__meta_kubernetes_pod_container_name"]
    target_label  = "container"
  }
}

// Tail pod log files from node filesystem
loki.source.kubernetes "pod_logs" {
  targets    = discovery.relabel.pod_logs.output
  forward_to = [loki.write.central.receiver]
}

// Write to central Loki in hub region via private NLB (VPC peering)
loki.write "central" {
  endpoint {
    // Replace with private NLB DNS of hub Loki for cross-cluster routing
    url = "http://internal-loki-nlb.us-east-1.elb.amazonaws.com/loki/api/v1/push"

    tenant_id = "prod-eu-west-1"  // Maps to per-tenant retention in Loki overrides

    basic_auth {
      username = env("LOKI_USERNAME")   // Injected via k8s secret
      password = env("LOKI_PASSWORD")
    }
  }

  external_labels = {
    cluster = "prod-eu-west-1",
    region  = "eu-west-1",
  }
}

Watch out: the storage_config.aws.s3 field requires an s3://bucket-name URI format. Using an ARN format silently falls back to filesystem storage and logs no error at startup. You won't notice until an Ingester pod restarts and you check S3 to find it empty.

For IAM, use IRSA (IAM Roles for Service Accounts) with s3:GetObject, s3:PutObject, s3:DeleteObject, and s3:ListBucket. Node-level instance profiles grant S3 access to every pod on the node — that's not acceptable in a production security posture. You can read more about the overall architecture approach on kuryzhev.cloud.

Advanced Patterns — Ruler, Retention Tiers, and Query Sharding

Once the basic topology is working, three features separate a functional setup from a scalable one.

Per-tenant retention is the most impactful cost control lever. Define an overrides.yaml that sets retention_period: 720h for the prod-eu-west-1 tenant and 168h (7 days) for dev-eu-west-1. The Compactor enforces these on a 24-hour cycle. Without per-tenant retention, every cluster's logs accumulate at the same rate in S3 indefinitely. One important note: the table_manager component is deprecated as of Loki 2.8. If you have both table_manager and compactor configured, retention silently does nothing. Use Compactor exclusively with retention_enabled: true set explicitly — it does not default to true.

The Ruler component enables cross-cluster alerting via LogQL recording and alerting rules. Configure ruler_storage pointing to a separate S3 prefix (not the same prefix as chunks — this causes read conflicts). A rule that fires when the error rate in eu-west-1 exceeds a threshold routes to Alertmanager in the hub region, giving you unified alert management regardless of which cluster generated the condition.

Query frontend sharding with max_query_parallelism: 32 and split_queries_by_interval: 15m is non-negotiable for multi-cluster deployments. Without it, a 24-hour LogQL query across 5 clusters serializes through a single Querier, hits the timeout, and returns a 500. With it, the Query Frontend splits the time range into 15-minute chunks and fans them out across Querier replicas in parallel. The difference between a 45-second timeout and a 4-second result on a wide query comes down to these two settings.

Also consider enabling the index_gateway component once your index exceeds roughly 10GB. It offloads index lookups from Queriers, reducing their memory footprint by approximately 40% in our measurements. This matters when you're running 3 Querier replicas and each one is holding the full index in memory.

Performance and Cost Notes

The ingestion cost driver in this architecture is not compute — it's S3 PUT requests. Loki's default chunk_target_size is 1.5MB (1,572,864 bytes). For clusters with moderate log volume, chunks fill slowly, flush frequently, and generate a high PUT-to-data ratio. We set chunk_target_size: 3145728 (3MB) and chunk_encoding: snappy across all hub Ingesters. Snappy compresses faster than gzip with slightly lower ratio — for log data the trade-off favors throughput.

Cross-region data transfer from eu-west-1 agents to us-east-1 Loki costs approximately $0.02/GB at standard AWS rates. At 100GB/day per spoke cluster, that's roughly $60/month per region before any optimization. VPC peering or AWS PrivateLink cuts this to $0.01/GB and eliminates public internet exposure entirely. For three spoke clusters at that volume, the saving is ~$90/month — enough to justify the peering setup time.

On the query side: a 1-hour LogQL filter query across 3 clusters with proper label indexing — meaning cluster, namespace, container labels set correctly — resolves in under 2 seconds in our environment. The same query using a regex match against pod_name (a high-cardinality label we index for legacy reasons) takes 45+ seconds and saturates Querier CPU. The lesson: pod_name belongs in log content extracted via | json, not in the label set. A query like {cluster="prod-eu-west-1", namespace="payments"} |= "ERROR" | json | line_format "{{.message}}" is fast. A query with pod_name=~"payments-.*" as a stream selector is slow.

One final security note: enable SSE-KMS on your S3 bucket and set BucketKeyEnabled: true. Without BucketKey, every object written generates a separate KMS API call. At scale — millions of chunks — this adds meaningful cost and introduces KMS throttling as a new failure mode for your log pipeline. The official Loki documentation covers storage configuration in detail, and the Grafana Alloy docs are the right reference for agent configuration going forward as Promtail approaches end-of-life.

Loki multi-cluster log aggregation across AWS regions is not complicated once you have the right mental model. One hub, regional agents, shared S3, tenant-based isolation. The mistakes all come from treating Loki like Prometheus or treating a temporary setup like it won't become permanent. Get the labels right from day one, run the Compactor as a single replica, and size your chunks for your actual volume.

Related