Deploy on AKS
This guide takes you from an empty Azure subscription to a running Zaris cluster on Azure Kubernetes Service (AKS) — creating the cluster, deploying the Helm chart, and then exposing the Web Console privately, over public HTTP, and finally over trusted HTTPS. It also covers pinning Zaris to a dedicated cache node pool and enabling cluster (data-plane) TLS.
This is a cloud-specific walkthrough. For how the chart works — the StatefulSet model, the connection-string client, scaling, and every value — read the Kubernetes overview first. This page assumes those concepts and focuses on the AKS-specific steps.
Prerequisites
- An Azure subscription with permission to create resource groups and AKS clusters.
- The Azure CLI (
az) andkubectl+helmv3 on your machine.- First time? Use Azure Cloud Shell. It runs in the browser with
az,kubectl, andhelmpreinstalled and already signed in — no local setup. The one exception:kubectl port-forward(used for the private console) needs a local terminal, because it forwards to your localhost.
- First time? Use Azure Cloud Shell. It runs in the browser with
Sign in & create a resource group
If you are using the Azure CLI locally, sign in first (Cloud Shell is already signed in):
az login
Create a resource group to hold everything — deleting it later removes the whole deployment in one step:
az group create -n zaris-rg -l eastus
Create the AKS cluster
Create a 3-node cluster. --tier free uses the free control-plane tier (you pay only for the worker VMs):
az aks create -g zaris-rg -n zaris-aks \
--node-count 3 \
--node-vm-size Standard_D4s_v7 \
--generate-ssh-keys \
--tier free
Subscriptions differ in which VM SKUs they may create in a region. If az aks create fails with "The VM size … is not allowed", the error message lists the sizes that are allowed — pick one of those and retry. Good general-purpose picks are the v7 D-series: Standard_D4s_v7 (4 vCPU / 16 GiB), or Standard_D2s_v7 (2 vCPU / 8 GiB) for a cheaper test cluster.
Fetch cluster credentials into your kubeconfig and confirm the nodes are Ready:
az aks get-credentials -g zaris-rg -n zaris-aks
kubectl get nodes
You should see 3 nodes in Ready state.
(Optional) Dedicated cache node pool
For production it is a common pattern to keep the cache tier on its own VMs, separate from your application tier — so cache pods get predictable resources and app pods never crowd them (or vice-versa). AKS models this with node pools plus a taint/label pair.
Create the cluster with its first pool named apps (this is where your application Deployments land by default), then add a dedicated cache pool:
# Cluster whose default pool is the app tier
az aks create -g zaris-rg -n zaris-aks \
--nodepool-name apps --node-count 6 \
--node-vm-size Standard_D4s_v7 \
--generate-ssh-keys --tier free
az aks get-credentials -g zaris-rg -n zaris-aks
# Add a cache pool: labelled workload=zaris and tainted so only Zaris lands on it
az aks nodepool add -g zaris-rg --cluster-name zaris-aks \
--name cache --node-count 3 \
--node-vm-size Standard_D4s_v7 \
--labels workload=zaris \
--node-taints workload=zaris:NoSchedule
How the two settings work together:
- The taint
workload=zaris:NoSchedulerepels everything from the cache nodes — ordinary app pods (which carry no matching toleration) will never schedule there. - The label
workload=zarisis what Zaris'snodeSelectortargets — it keeps Zaris pinned to the cache pool. - Because Zaris also carries a toleration for that taint, it is the one workload allowed onto the cache nodes. Your application
Deployments need no change — they simply keep landing on theappspool.
Pin Zaris to the cache pool with a values file (used in the deploy step below):
# cache-pool.yaml — pin the whole chart (nodes + manager) to the cache pool
node:
nodeSelector:
workload: zaris
tolerations:
- key: workload
operator: Equal
value: zaris
effect: NoSchedule
manager:
nodeSelector:
workload: zaris
tolerations:
- key: workload
operator: Equal
value: zaris
effect: NoSchedule
Add -f cache-pool.yaml to the helm install in the next section.
With RF 2, node.replicas should be a multiple of the replication factor so every partition has both copies on distinct pods. On a 3-node cache pool you have three sensible choices:
- RF 3 (
--set node.replicationFactor=3 --set node.replicas=3) — every partition has a copy on all three nodes; survives 2 node losses. Simplest for exactly 3 nodes. - RF 2, replicas 3 — the chart warns that one partition ends up single-copy (3 ÷ 2 doesn't divide evenly); tolerable for non-critical data, but not fully durable.
- 4 cache nodes (
--node-count 4) with RF 2, replicas 4 — clean 2× replication with room to lose any one node.
Deploy Zaris (private first)
Start private and unauthenticated — the safe default. Create a namespace and install the chart (the store is auto-attached on deploy):
kubectl create namespace zaris
helm install zaris oci://registry-1.docker.io/clustron/zaris -n zaris \
--set node.replicas=4 \
--set node.replicationFactor=2
The chart image is public — pulling it needs no Docker login. Add -f cache-pool.yaml here if you set up a dedicated cache pool above.
Wait for the nodes to become Ready (a Ready pod already owns and serves its share of the data):
kubectl -n zaris rollout status statefulset/zaris
Open the console privately with a port-forward — this needs no security because only you, through your kubeconfig, can reach it:
kubectl -n zaris port-forward svc/zaris-manager-console 8080:7810
# → http://localhost:8080
kubectl port-forward tunnels to your localhost, so run it from a local terminal — not Azure Cloud Shell. A 503 for the first few seconds is normal; the console URL is readiness-gated and goes live once a store is attached and serving.
Expose the console publicly (LoadBalancer, HTTP)
To reach the console without a port-forward, give it a public cloud LoadBalancer. External exposure always requires control-plane security — the chart refuses to render loadBalancer (or ingress) unless manager.security.enabled=true with an admin credential, so you can never accidentally put an unauthenticated admin console on the internet.
helm upgrade zaris oci://registry-1.docker.io/clustron/zaris -n zaris --reuse-values \
--set manager.console.expose=loadBalancer \
--set manager.security.enabled=true \
--set manager.security.adminUsername=admin \
--set manager.security.adminPassword='<change-me>' \
--set 'manager.console.loadBalancer.sourceRanges={203.0.113.7/32}' # your IP — recommended
sourceRanges is a CIDR allowlist — restrict it to your IP (/32) rather than leaving the console open to the whole internet. The manager self-provisions the admin before the pod is Ready, so the URL is never published un-provisioned; still, change admin/admin — those defaults must not ship. In production prefer --set manager.security.existingSecret=<secret> (a Secret with keys admin-username/admin-password) over an inline password.
Watch for the public IP to be assigned:
kubectl -n zaris get svc zaris-manager-console -w
When EXTERNAL-IP changes from <pending> to an address, browse to http://<external-ip> and sign in.
A LoadBalancer is a layer-4 (TCP) load balancer — it does not terminate TLS, so the console is served over plain HTTP. Fine for a quick internal demo; for anything real, use the Ingress + HTTPS path below.
Console over HTTPS (Ingress + cert-manager)
HTTPS requires two things a bare LoadBalancer can't give you: an Ingress (where TLS is terminated) and a DNS hostname (a certificate is issued for a name, not an IP). The flow is: install an ingress controller → point DNS at it → install cert-manager → issue a trusted cert automatically.
1. Install the ingress-nginx controller (creates its own public LoadBalancer):
helm install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
-n ingress-nginx --create-namespace
2. Get the controller's external IP:
kubectl -n ingress-nginx get svc ingress-nginx-controller -w
3. Point DNS at it. Create a DNS A record mapping your hostname to that IP:
zaris-console.example.com → <ingress-external-ip>
4. Install cert-manager (mints and auto-renews the certificate):
helm install cert-manager cert-manager \
--repo https://charts.jetstack.io \
-n cert-manager --create-namespace \
--set crds.enabled=true
5. Apply a Let's Encrypt ClusterIssuer (HTTP-01 challenge, solved through the nginx ingress):
kubectl apply -f - <<'EOF'
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
EOF
6. Deploy the console as an Ingress with TLS:
helm upgrade zaris oci://registry-1.docker.io/clustron/zaris -n zaris --reuse-values \
--set manager.console.expose=ingress \
--set manager.console.ingress.className=nginx \
--set manager.console.ingress.host=zaris-console.example.com \
--set manager.console.ingress.tls.enabled=true \
--set manager.console.ingress.tls.clusterIssuer=letsencrypt-prod \
--set manager.security.enabled=true \
--set manager.security.adminPassword='<change-me>'
cert-manager sees the Ingress, satisfies the HTTP-01 challenge, and issues the certificate (a minute or two the first time). The result: https://zaris-console.example.com with a publicly-trusted cert and no browser warning. TLS terminates at the ingress using that cert — never Zaris's internal CA.
If you already hold a cert, skip cert-manager and set --set manager.console.ingress.tls.secretName=<your-tls-secret> instead of clusterIssuer. The Secret must be a standard kubernetes.io/tls Secret in the zaris namespace.
Cluster TLS (node-to-node + client encryption)
The console TLS above secures the browser↔console hop. A separate, independent layer secures the data plane — node↔node replication and client↔node traffic — via node.tls.enabled with a cluster CA and one per-node leaf certificate mounted from a Secret.
Generating the CA and per-node leaves is covered in depth in the security docs — follow those to produce ca.pem and a Secret of <nodeId>.pfx leaves:
- CA and trust modes — the four trust models and which to pick.
- Certificate management — generating the CA + per-node multi-SAN leaves and creating the Secret.
Once you have the CA and the cert Secret, enable data-plane TLS:
helm upgrade zaris oci://registry-1.docker.io/clustron/zaris -n zaris --reuse-values \
--set node.tls.enabled=true \
--set node.tls.certSecret=zaris-node-certs \
--set-file node.tls.caPem=ca.pem
node.tls.certSecret is a Secret whose keys are per-node <nodeId>.pfx leaves; node.tls.caPem is supplied from a local file with --set-file. Clients then connect with the zariss:// scheme and the cluster CA:
zariss://<host>:7861/zaris-k8s?ca=/path/ca.pem
Connect your app
There is one store per release, named by cluster.id (default zaris-k8s) — that value is both the cluster id and the store name your clients use. In-cluster applications connect through the client Service DNS:
# plaintext
zaris://zaris-client.zaris.svc.cluster.local:7861/zaris-k8s
# TLS (when cluster TLS is enabled)
zariss://zaris-client.zaris.svc.cluster.local:7861/zaris-k8s?ca=/path/ca.pem
Replace zaris in the DNS name with your namespace, and zaris-k8s with your cluster.id if you changed it. See the Kubernetes overview for the C# / ASP.NET and PowerShell client examples.
Scale
Scaling has two independent dimensions on AKS:
More VMs (capacity for pods) — scale the node pool:
az aks nodepool scale -g zaris-rg --cluster-name zaris-aks --name cache --node-count 4
More Zaris pods (capacity + partitions) — scale the chart. Scaling out is lossless resharding — new pods join and the partition map grows onto them with no restart of existing pods:
helm upgrade zaris oci://registry-1.docker.io/clustron/zaris -n zaris --reuse-values \
--set node.replicas=6
Keep node.replicas a multiple of node.replicationFactor. Scale in one step at a time (it is not yet drain-safe) — see the scaling notes.
Tear down (stop billing)
The worker VMs, load balancers, and public IPs cost money even when nothing is using them. When you are finished, delete the resource group — it removes the cluster and everything created in this guide in one step:
az group delete --name zaris-rg --yes --no-wait
--no-wait returns immediately while Azure deletes in the background. Double-check with az group list -o table afterwards. This is irreversible — it deletes the AKS cluster, all node pools, and any data still in the cluster.