Azure AKS Guide
Deploy KubeBolt on Azure Kubernetes Service, including AGIC ingress and Azure Monitor managed Prometheus.
Quick install
The chart works out of the box on AKS — it creates a ServiceAccount and a ClusterRole granting KubeBolt read access to the cluster plus the write verbs its actions use (exec, port-forward, scale, restart, edit, delete).
helm install kubebolt oci://ghcr.io/clm-cloud-solutions/kubebolt/helm/kubebolt \
--namespace kubebolt --create-namespace --wait
kubectl -n kubebolt get secret kubebolt-admin-password \
-o jsonpath='{.data.password}' | base64 -d ; echo
kubectl -n kubebolt port-forward svc/kubebolt 3000:80
Open http://localhost:3000 and log in as admin with the printed password
(other ways to get it: Quick Start).
AKS enables Metrics Server by default. Confirm with
kubectl get apiservice v1beta1.metrics.k8s.io.
Authentication
KubeBolt itself needs no Azure permissions — it talks to the Kubernetes API and nothing else. Workload Identity matters only when the ServiceAccount also has to reach an Azure service, which for KubeBolt means one case: reading Azure Monitor managed Prometheus with the agent, below.
The chart can annotate KubeBolt’s ServiceAccount if your platform conventions
require it — --set serviceAccount.annotations."azure\.workload\.identity/client-id"=<CLIENT_ID>
— but that alone does not federate anything, and KubeBolt has no Azure call to
make. The full Workload Identity chain is set up once, for the agent, in
Azure Monitor managed Prometheus below.
Azure RBAC for Kubernetes. If the cluster authorizes through Azure RBAC instead of native Kubernetes RBAC, the chart’s ClusterRole still applies — the two coexist, and the chart’s ServiceAccount uses native Kubernetes RBAC.
Networking. Both kubenet and Azure CNI work with no extra configuration.
Ingress
With the Application Gateway Ingress Controller:
helm install kubebolt \
oci://ghcr.io/clm-cloud-solutions/kubebolt/helm/kubebolt \
--namespace kubebolt --create-namespace \
--set ingress.enabled=true \
--set ingress.className=azure-application-gateway \
--set ingress.hosts[0].host=kubebolt.example.com \
--set ingress.hosts[0].paths[0].path=/ \
--set ingress.hosts[0].paths[0].pathType=Prefix
With ingress-nginx, which is common on AKS, swap the class:
--set ingress.className=nginx
The chart also supports the Gateway API instead of an Ingress —
--set gatewayAPI.enabled=true, mutually exclusive with ingress.
Azure Monitor managed Prometheus
If your metrics already live in an Azure Monitor Workspace, deploy the optional agent in promread mode and it reads the metric families straight from the workspace, authenticating with Azure Workload Identity — no second scrape pipeline and no secret to rotate.
The cluster needs three things enabled: --enable-oidc-issuer,
--enable-workload-identity, and --enable-azure-monitor-metrics (which
provisions the workspace and the ama-metrics scrapers).
1. Find the endpoint
Endpoints are workspace- and region-scoped, with a random suffix Azure picks at provisioning time, so look it up rather than guessing:
AMW_ID=$(az resource list \
--resource-type "Microsoft.Monitor/accounts" \
--query "[0].id" -o tsv)
AMW_ENDPOINT=$(az resource show --ids $AMW_ID \
--query "properties.metrics.prometheusQueryEndpoint" -o tsv)
TOKEN=$(az account get-access-token \
--resource "https://prometheus.monitor.azure.com" \
--query accessToken -o tsv)
curl -s -H "Authorization: Bearer $TOKEN" \
"${AMW_ENDPOINT}/api/v1/query?query=count(up)"
A status: success response means the endpoint and the bearer-auth path work.
2. Create the managed identity and the federated credential
Azure needs the most wiring of the three clouds, because each piece lives in a different control plane:
RG=<your-aks-resource-group>
CLUSTER=<your-aks-cluster-name>
LOCATION=eastus
NAMESPACE=kubebolt
KSA=kubebolt-agent # the chart's default; override with serviceAccount.name
UAMI=kubebolt-promread
# 1. The user-assigned managed identity.
az identity create --name "$UAMI" --resource-group "$RG" --location "$LOCATION"
UAMI_CLIENT_ID=$(az identity show -g "$RG" -n "$UAMI" --query clientId -o tsv)
UAMI_PRINCIPAL_ID=$(az identity show -g "$RG" -n "$UAMI" --query principalId -o tsv)
# 2. Read-only access to the workspace.
az role assignment create \
--assignee-object-id "$UAMI_PRINCIPAL_ID" \
--assignee-principal-type ServicePrincipal \
--role "Monitoring Data Reader" \
--scope "$AMW_ID"
# 3. Trust the cluster's OIDC issuer for that ServiceAccount.
OIDC_ISSUER=$(az aks show -g "$RG" -n "$CLUSTER" \
--query "oidcIssuerProfile.issuerUrl" -o tsv)
az identity federated-credential create \
--name "${UAMI}-fed" \
--identity-name "$UAMI" \
--resource-group "$RG" \
--issuer "$OIDC_ISSUER" \
--subject "system:serviceaccount:${NAMESPACE}:${KSA}" \
--audience api://AzureADTokenExchange
Verify before moving on — a silent mis-wire here surfaces as a cryptic
AZURE_FEDERATED_TOKEN_FILE not set at runtime:
az role assignment list --assignee "$UAMI_PRINCIPAL_ID" --scope "$AMW_ID" \
--query "[].roleDefinitionName" -o tsv # → Monitoring Data Reader
az identity federated-credential show \
--name "${UAMI}-fed" --identity-name "$UAMI" -g "$RG" \
--query "{subject:subject,issuer:issuer,audiences:audiences}"
3. Install the agent in promread mode
helm upgrade --install kubebolt-agent \
oci://ghcr.io/clm-cloud-solutions/kubebolt/helm/kubebolt-agent \
-n kubebolt --create-namespace \
--set backendUrl=<your-kubebolt-host>:443 \
--set tls.enabled=true \
--set cluster.name=<your-cluster-name> \
--set auth.mode=ingest-token \
--set auth.ingestToken.existingSecret=kubebolt-ingest-token \
--set scrape.enabled=false \
--set agent.promRead.enabled=true \
--set agent.promRead.url="${AMW_ENDPOINT}" \
--set agent.promRead.auth.mode=azureWorkloadIdentity \
--set serviceAccount.annotations."azure\.workload\.identity/client-id"="${UAMI_CLIENT_ID}" \
--set-string podLabels."azure\.workload\.identity/use"="true"
Notes that catch people out:
backendUrlis a gRPC dial target,host:port, no scheme. With a TLS-terminating proxy in front of the backend,tls.enabled=trueis required as well — omitting it looks like the host is unreachable.agent.promRead.urlis the base endpoint; the agent appends/api/v1/query_rangeitself.scrape.enabled=falseturns off the bundledvmagentsidecar, not the DaemonSet’s kubelet collectors.- The chart auto-defers the in-agent node network and NodeStress (load +
PSI) collectors when
agent.promRead.auth.mode=azureWorkloadIdentity, because Azure’sama-metrics-nodealready scrapes node-exporter. You don’t needagent.deferNodeStress=trueoragent.deferNodeNetwork=trueby hand; drop them if you carried them forward from an older values file. - The
kubebolt-ingest-tokenSecret must exist in the agent’s namespace, with the token under thetokenkey (issue it in Administration → Agents & Ingest → Agent Tokens).
The Add cluster wizard generates this command for you — see Connecting clusters and Metrics.
Troubleshooting
API pod in CrashLoopBackOff. Check kubectl -n kubebolt logs -l app.kubernetes.io/component=api.
The usual cause is a missing ClusterRoleBinding — verify with
kubectl get clusterrolebinding | grep kubebolt.
Image pull errors behind a private ACR. Allow ghcr.io, or mirror the
images and override api.image.repository and web.image.repository (and
metrics.storage.embedded.image.repository for VictoriaMetrics) in your values:
az acr import --name myacr --source ghcr.io/clm-cloud-solutions/kubebolt/api:<tag>
az acr import --name myacr --source ghcr.io/clm-cloud-solutions/kubebolt/web:<tag>
403 on some resources. Expected, and handled: KubeBolt degrades gracefully. Restricted resources are dimmed in the sidebar behind a “Limited access” banner. See RBAC & Permissions.