Multi-Tenancy Isolation in VictoriaMetrics, VictoriaLogs and Grafana: Building Client Separation in a Shared Stack

Voltir runs a single shared monitoring stack consisting of VictoriaMetrics, VictoriaLogs, Grafana and vmalert for all customers instead of deploying separate instances per tenant. Isolation relies on numeric account IDs enforced at every layer: nginx validates tokens against the platform API before proxying writes, Grafana data sources hard-code tenant accounts, and vmalert rules receive automatic account filters. The team discovered that a single malformed rule or template could break alerting for every tenant because vmalert loads the entire rules directory at once. They also found that Grafana automatically created personal organizations granting Org Admin rights, accumulating hundreds of orphaned organizations and users that consumed disk space and resources. A dedicated cleanup module plus hourly reconciliation now removes rules, organizations, series and users when a tenant is deleted, while isolation tests verify both data and API objects across tenants.

HabrTranslated from Russian

Read at source

Voltir provides monitoring for servers, 1C systems and databases as both a cloud service and on-premise installation. In the cloud all clients share one stack containing vmstorage, vmselect, vminsert, VictoriaLogs, Grafana, vmalert, Alertmanager and Postgres. Separate stacks per customer would require ten full copies of caches and ten update cycles per release, so the company keeps a single stack and implements isolation manually at each layer.

Every tenant receives a numeric account_id. VictoriaMetrics routes writes and reads through paths such as /insert/<account>/… and /select/<account>/…, while VictoriaLogs and VictoriaTraces read the account from the AccountID header. Nginx sits in front and calls the platform API via auth_request to determine the tenant from the Bearer token before forwarding requests. Storage endpoints are never exposed externally.

Each tenant has its own Grafana organization whose data sources are created by the platform with the account ID embedded in the URL or secureJsonData header. Users receive at most the Editor role; Org Admin is deliberately withheld because an administrator could change the data-source URL or header and read another tenant’s metrics or logs. The platform explicitly checks role values and rejects anything higher than Editor for tenant users.

Write authorization compares the account supplied in the URL with the account belonging to the token; a mismatch returns 403. For logs and traces the platform returns the correct account in the auth_request response and nginx overwrites any client-supplied header. VictoriaLogs only understands the AccountID header and silently ignores X-Tenant-ID, writing everything to account 0 if the header is missing.

A single vmalert instance reads all rule files. Each tenant’s rules file contains groups narrowed by an extra_filters clause containing the tenant’s account ID. VictoriaMetrics appends this filter to every selector, preventing a rule from seeing data outside its tenant. Global rules without filters were removed after one self-test rule began scanning thousands of deleted tenants and consuming excessive CPU.

During testing a rule containing the Russian string “пять минут” instead of a valid duration value caused vmalert to reject the entire rules directory on reload. Subsequent changes to other tenants stopped being applied, and a container restart would have left alerting completely disabled. The platform now validates durations, severity levels and Go templates before writing rule files, performs atomic replacement under lock, and reverts on reload failure.

Deleted tenants left behind rule files, Grafana organizations, users and time series. Over several months the team accumulated 196 orphaned rule files, 705 phantom Grafana users and more than three thousand stale accounts visible in /admin/tenants. A single cleanup module now removes every external artifact when a tenant is deleted, and an hourly reconciliation job compares files, organizations and storage accounts against the database, failing CI if any residue remains.

Resource isolation remains incomplete. All tenants share memory, CPU and disk. Heavy parallel queries from one dashboard or high-cardinality series from a single Kubernetes cluster can still trigger OOM events that affect every tenant. Per-container memory limits, reduced cache sizes in VictoriaLogs and request queuing mitigate but do not eliminate the shared-resource risk.

Meridian digestThe week's most consequential security stories, distilled into one briefing.See the digest

More in Other

Mail.ru Reduces Lifetime 1TB Cloud Storage Promo to Standard 8GB Quota for Select Users
Kimi K3 Open Model Surpasses Closed AI Leaders with Million-Token Context and 896 Experts
Innostage Completes Functional Testing of PT NGFW 1.10.3 Across 156 Criteria