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.