Skip to content

Azure

Deploy the Retriever to AKS and query blobs that already sit in an Azure Storage account. The retriever reads what your forwarder or analyzer lands in Blob Storage, indexes it in place, and serves queries over it.

Indexing is event driven. An Event Grid subscription raises BlobCreated on the input container, and four Azure Storage Queues carry index, query, sub-query and stream work between the retriever's roles, so a new blob is indexed as it arrives.

Pods authenticate with workload identity. An Entra identity federated to the pod's Kubernetes service account issues short-lived tokens at run time, which is why the install below grants roles to an identity rather than configuring a key.

The storage account must have hierarchical namespace off. ADLS Gen2 sorts / lowest, which breaks the byte-lexicographic ordering the index scan depends on, and the accessor rejects such an account at construction.

Step 1: Prerequisites
Requirement Description
10x License Optional. With none set, the engine runs the built-in evaluation license
Azure subscription Permission to create resource groups, storage accounts, managed identities, role assignments, and Event Grid system topics
CLI tools az, kubectl, helm
Storage account StorageV2 with hierarchical namespace off, or let the provisioning script create one
AKS cluster With the OIDC issuer and workload identity enabled, or let the provisioning script create one
Step 2: Provision

The provisioning script ships inside the chart. Pull and unpack it:

helm repo add log10x https://log-10x.github.io/helm-charts

helm pull log10x/retriever-10x --version 1.0.24 --untar

One run creates the Azure side and writes the Helm values file:

The chart archive stores every file without the executable bit, so run the script through bash.

bash retriever-10x/scripts/azure/provision-retriever.sh \
  --resource-group tenx-rg \
  --location eastus \
  --account tenxlogs \
  --create-aks tenx-aks \
  --namespace log10x-retriever \
  --release tenx-retriever \
  --values-out values/azure.yaml

Pass --aks NAME instead of --create-aks NAME to use a cluster that already has the OIDC issuer and the workload identity webhook. See the Components section below for the resources the script creates.

Subscriptions differ in which VM sizes they are allowed to create. --node-size defaults to Standard_D2s_v7, and a subscription that does not allow that size fails the cluster create with a message from Azure rather than from the script:

The VM size of Standard_D2s_v7 is not allowed in your subscription in location 'eastus'

On that failure the script prints the sizes the subscription does allow in that location. Re-run with --node-size set to one of them.

The script writes the values file to --values-out, creating the directory for it, and prints the helm install, kubeconfig, first-query and result-download commands.

Optional flags:

Flag Default
--input-container logs
--index-container tenx-index
--index-path tenx
--queue-prefix tenx
--image-tag 1.1.78
--node-size Standard_D2s_v7
Step 3: Install

Point kubectl at the cluster first, so the release lands on the AKS cluster rather than on whatever context kubectl already has:

az aks get-credentials --resource-group tenx-rg --name tenx-aks \
  --file values/tenx-aks.kubeconfig --overwrite-existing
export KUBECONFIG=values/tenx-aks.kubeconfig

The emitted values file, with the defaults above:

values/azure.yaml
# The service account the chart creates is named for the release, which is
# what the federated credential subject binds to.
fullnameOverride: tenx-retriever

image:
  tag: "1.1.78"

storage:
  provider: azure
  azure:
    account: tenxlogs
    indexContainer: "tenxlogs/tenx-index/tenx"
    inputContainer: logs
    invoke: queue
    queues:
      index: tenx-index
      query: tenx-query
      subquery: tenx-subquery
      stream: tenx-stream
    auth:
      method: workloadIdentity
      clientId: "<managed identity client id>"
      tenantId: "<entra tenant id>"

scheduledQueries:
  enabled: false

Install:

helm install tenx-retriever log10x/retriever-10x --version 1.0.24 \
  --namespace log10x-retriever --create-namespace \
  -f values/azure.yaml

log10xApiKey is optional, and empty runs the built-in evaluation license. Add --set-string log10xApiKey="$LOG10X_API_KEY" for a production license, which keeps the key out of the values file.

The emitted file pins image.tag to an engine release. Never set it to latest, and pin the same field in a values file written by hand, because an unset tag falls back to the chart's appVersion, an engine release that predates Azure support. On storage.provider: azure the chart adds the azure.workload.identity/use pod label and the azure.workload.identity/client-id service account annotation, and the webhook injects AZURE_CLIENT_ID, AZURE_TENANT_ID and AZURE_FEDERATED_TOKEN_FILE. queryLogGroup is an AWS CloudWatch Logs setting. Leave it unset on Azure.

Step 4: Verify Indexing and Querying

Wait for the pod:

kubectl -n log10x-retriever get pods -w

The image is a JVM build, so the pod takes tens of seconds to become ready. The script granted the signed-in operator Storage Blob Data Contributor and Storage Queue Data Contributor on the account, so --auth-mode login works below unless the run passed --no-operator-roles.

Upload one log file to the input container:

echo "{\"timestamp\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"level\":\"ERROR\",\"message\":\"Test error\"}" > test.log

az storage blob upload --auth-mode login \
  --account-name tenxlogs --container-name logs \
  --name app/test.log --file test.log

Plain text lines and JSON lines both index. A plain text line carries its own timestamp first:

2026-09-13T15:32:25Z ERROR OrderService failed to settle order ORD-8842317 after 3 attempts

The ISO-8601 UTC timestamp at the start of the line is what the query window is evaluated against. A level word in the event text, ERROR, WARN or INFO, sets severity_level, which is the field the query below filters on.

Event Grid puts a BlobCreated event on the tenx-index queue and an index pod picks it up. IndexFilterStats - index complete carries the bytes read and the filter count, and IndexFilterWriter - index written follows it with one line per blob indexed:

kubectl logs -n log10x-retriever -l app=retriever-10x \
  -c retriever-10x-all-in-one --tail=50
[INFO ] 2026-09-13 14:31:50.112 [executor-thread-1] IndexFilterStats - index complete. Bytes: 354, filters.size: 1, ...
[INFO ] 2026-09-13 14:31:50.128 [executor-thread-1] IndexFilterWriter - index written: object=app/test.log, byteRanges=1, filters=1, values=34, minTimestamp=..., maxTimestamp=...

Then enqueue a query on the query queue as raw JSON. The name field must equal the first path segment of the blob name, so app/test.log is queried as "name":"app", and any other value matches nothing and reports nothing. writeResults tells the stream role to write matched events back to the index container as JSONL:

az storage message put --auth-mode login \
  --account-name tenxlogs --queue-name tenx-query \
  --content '{"name":"app","from":"now(\"-1h\")","to":"now()","search":"severity_level==\"ERROR\"","writeResults":true}'

The query window is evaluated against the timestamps inside the log lines, so lines older than the window return nothing. Upload a log with current timestamps, or widen from.

Results land in the index container at <index-path>/tenx/<name>/qr/<queryId>/<fromEpochMs>_<toEpochMs>/<id>.jsonl. With the defaults above the listing prefix is tenx/tenx/app/qr/:

az storage blob list --auth-mode login \
  --account-name tenxlogs --container-name tenx-index \
  --prefix tenx/tenx/app/qr/ --output table

Download one of the listed .jsonl blobs to read the matches:

az storage blob download --auth-mode login \
  --account-name tenxlogs --container-name tenx-index \
  --name <blob> --file results.jsonl

Each line is one matched event, carrying text with the event as read, severity_level with the level the indexer inferred, and message_pattern with the pattern the event belongs to.

A _DONE.json marker sits under the same prefix. The coordinator writes that marker once it has dispatched the sub-queries, ahead of the result blobs, and its counters do not describe what the stream role went on to write. Poll the prefix for .jsonl files rather than reading the marker as a completion signal.

Step 5: Other Credentials

Workload identity is the default. Exactly one credential may be set, and it must match storage.azure.auth.method. Pass the value with --set-string so it stays out of the values file.

An Entra application rather than a managed identity. Set auth.method: servicePrincipal with clientId, tenantId and clientSecret, which the chart mounts as AZURE_CLIENT_SECRET.

Set auth.method: accountKey with accountKey, which the chart mounts as AZURE_STORAGE_KEY. An account key carries full control of the account. Use it for short-lived tests, and workload identity or a service principal for standing deployments.

Set auth.method: sasToken with sasToken, which the chart mounts as AZURE_STORAGE_SAS_TOKEN. A SAS token cannot read account properties, so the namespace check is skipped. Issue the token against a flat-namespace account.

Point storage.azure.auth.secret.existingSecret at a secret you manage to keep the credential outside the release.

Step 6: Expected Log Lines

The namespace check calls Get Account Information, an account-level operation covered by control-plane roles rather than by data-plane roles such as Storage Blob Data Contributor. The call answers 403, the check treats the account as flat-namespace, and each accessor construction logs one line. One line per pipeline run is expected.

[WARN ] 2026-09-13 14:31:49.897 [executor-thread-2] AzureBlobClient - could not read account information for tenxlogs, status 403; assuming a flat namespace

The caller logs a second line for the same reason:

[WARN ] 2026-09-13 14:31:49.903 [executor-thread-2] AzureIndexAccess - could not determine the namespace of storage account tenxlogs; the index requires a flat namespace
Step 7: Teardown
helm uninstall tenx-retriever -n log10x-retriever

bash retriever-10x/scripts/azure/provision-retriever.sh \
  --resource-group tenx-rg --destroy

--destroy removes what the script created, including the blobs in both containers. A cluster passed with --aks is left alone.

Components

What the provisioning script creates. Tabs describe each component and what it's for.

A StorageV2 account in the resource group, hierarchical namespace off, with a TLS 1.2 floor. Pass --account to name it. The index scan reads listings in byte-lexicographic order, which is what a flat namespace gives.

Two blob containers on that account. logs holds the source logs the forwarder or analyzer lands. tenx-index holds the Bloom filter and reverse-index artifacts, and the JSONL results of queries that set writeResults. Rename either with --input-container and --index-container.

Four queues, tenx-index, tenx-query, tenx-subquery and tenx-stream, buffer work between the index, query and stream roles. A pod crash does not drop work in flight. The next poller picks it up. Change the prefix with --queue-prefix.

The pod identity. A user-assigned managed identity holding Storage Blob Data Contributor and Storage Queue Data Contributor on the account, with a federated credential for system:serviceaccount:<namespace>:<release> so the service account the chart creates can exchange its projected token for an Azure token. The script grants the same two roles to the signed-in operator, which --no-operator-roles skips. --create-aks also creates the cluster with the OIDC issuer and the workload identity webhook that make the exchange work.

Triggers indexing. A system topic on the storage account with a Microsoft.Storage.BlobCreated subscription, scoped to the input container and delivering to the tenx-index queue, so the indexer's own writes to the index container cannot re-trigger it.

Installs the retriever-10x chart from the emitted values file, which in turn creates pods, HPAs, Services, the service account the federated credential binds to, and optional CronJobs for scheduled queries.