Skip to main content

Deploy on GKE

This guide takes you from an empty Google Cloud project to a running Zaris cluster on Google Kubernetes Engine (GKE) — creating the cluster with gcloud, 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.

New to the chart? Start with the Kubernetes overview

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 GKE-specific steps.

Prerequisites

  • A Google Cloud project with billing enabled and permission to create GKE clusters.
  • The Google Cloud CLI (gcloud) plus kubectl + helm v3 on your machine.
    • First time? Use Google Cloud Shell. It runs in the browser with gcloud, kubectl, and helm preinstalled 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.

Enable the GKE (Kubernetes Engine) API, and set your project and a default zone so later commands can omit them:

gcloud services enable container.googleapis.com

gcloud config set project <your-project-id>
gcloud config set compute/zone us-central1-a

Create the GKE cluster

For a test, create a standard zonal cluster — a single zone keeps it simple and cheap, and Standard mode (unlike Autopilot) gives you the node-pool control the cache-tier section below relies on:

gcloud container clusters create zaris-gke \
--zone us-central1-a \
--num-nodes 3 \
--machine-type e2-standard-4
Autopilot vs Standard

GKE also offers Autopilot, where Google manages nodes for you and you pay per-pod. It is a great default for general workloads, but this guide uses Standard because the dedicated cache node pool below needs direct control over node pools, labels, and taints — which Autopilot abstracts away.

Fetch cluster credentials into your kubeconfig and confirm the nodes are Ready:

gcloud container clusters get-credentials zaris-gke --zone us-central1-a
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). GKE models this with node pools plus a taint/label pair.

Add a dedicated cache pool to the cluster, labelled and tainted so only Zaris lands on it:

gcloud container node-pools create cache \
--cluster zaris-gke \
--zone us-central1-a \
--num-nodes 3 \
--machine-type e2-standard-4 \
--node-labels workload=zaris \
--node-taints workload=zaris:NoSchedule

How the two settings work together:

  • The taint workload=zaris:NoSchedule repels everything from the cache nodes — ordinary app pods (which carry no matching toleration) will never schedule there.
  • The label workload=zaris is what Zaris's nodeSelector targets — 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 the cluster's default pool.

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.

Durability on exactly 3 cache nodes

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 (--num-nodes 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
port-forward needs a local terminal

kubectl port-forward tunnels to your localhost, so run it from a local terminal — not 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. On GKE a Service of type=LoadBalancer provisions a regional external network load balancer with a public IP automatically. 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.

This is HTTP, not HTTPS

A network 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 one of the HTTPS paths below.

Console over HTTPS

HTTPS requires two things a bare LoadBalancer can't give you: something that terminates TLS and a DNS hostname (a certificate is issued for a name, not an IP). GKE gives you two ways to get there — a portable ingress-nginx path, and a Google-native managed-certificate path.

This path is portable across any Kubernetes cluster and is the best starting point. 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 external network 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 (in Cloud DNS, or whatever provider hosts your zone):

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.

Bring your own certificate

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.

(b) Google-managed certificate + GKE Ingress

If you prefer Google's native path, GKE's built-in Ingress provisions a global external Application Load Balancer (GCLB) that terminates TLS using a Google-managed certificate — no cert-manager, no ingress controller to install.

  1. Reserve a global static IP and create a DNS A record for your host pointing at it:

    gcloud compute addresses create zaris-console-ip --global
    gcloud compute addresses describe zaris-console-ip --global --format='value(address)'
  2. Create a ManagedCertificate referencing the same host:

    kubectl apply -f - <<'EOF'
    apiVersion: networking.gke.io/v1
    kind: ManagedCertificate
    metadata:
    name: zaris-console-cert
    namespace: zaris
    spec:
    domains:
    - zaris-console.example.com
    EOF
  3. Deploy with manager.console.expose=ingress, but note the className and annotations differ from path (a): GKE Ingress uses --set manager.console.ingress.className=gce and needs annotations wiring the static IP (kubernetes.io/ingress.global-static-ip-name) and the managed cert (networking.gke.io/managed-certificates). Set these via manager.console.ingress.annotations in a values file, and leave manager.console.ingress.tls.enabled off (GCLB terminates TLS from the ManagedCertificate, not from a cluster Secret). Google-managed certs can take 15–60 minutes to go Active on first provisioning.

For a first deployment, path (a) is simpler and provisions faster; reach for (b) when you specifically want a Google-managed cert and the global load balancer.

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:

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 GKE:

More VMs (capacity for pods) — resize the cluster or a specific node pool:

# resize the whole cluster's default pool
gcloud container clusters resize zaris-gke --zone us-central1-a --num-nodes 4

# or resize just the cache pool
gcloud container clusters resize zaris-gke --zone us-central1-a \
--node-pool cache --num-nodes 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)

An idle GKE cluster still bills for its VMs

The worker VMs, load balancers, and static IPs cost money even when nothing is using them, and GKE also charges a per-cluster management fee beyond the free tier. When you are finished, delete the cluster — it removes the cluster, all node pools, and the load balancers created in this guide in one step:

gcloud container clusters delete zaris-gke --zone us-central1-a

If you reserved a global static IP for the Google-managed cert path, release it too, and double-check nothing lingers:

gcloud compute addresses delete zaris-console-ip --global
gcloud container clusters list

This is irreversible — it deletes the GKE cluster, all node pools, and any data still in the cluster.