MAQPNADocs

Install on Google GKE

Install MAQPNA on Google Kubernetes Engine step by step, with GKE Sandbox for tier-0, Dataplane V2, Workload Identity Federation, a locked GCS audit bucket and a passing doctor check.

This page installs MAQPNA on Google Kubernetes Engine (GKE) Standard. You create the cluster with Dataplane V2 and Workload Identity Federation, add a GKE Sandbox (gVisor) node pool and an optional Kata pool, grant the gateway's ServiceAccount its Google roles, and install the chart with the values-gke.yaml profile on top of the production profile.

The generic steps (Secrets, values, OIDC, the PostgreSQL state backend) are in Production install with Helm. This page adds what is specific to GKE.

What you get#

Trust tier Runtime on GKE Node pool RuntimeClass
tier-0 (gVisor) GKE Sandbox pool with --sandbox type=gvisor gvisor (created by GKE)
tier-1 (microVM) Kata from kata-deploy (not supported by Google) pool with --enable-nested-virtualization kata-qemu (created by kata-deploy)
tier-2 (confidential VM) not created — —

The profile turns off the chart's RuntimeClasses: GKE Sandbox creates gvisor itself. Without kata-deploy, tier-1 sessions stay Pending. Confidential GKE Nodes (Intel TDX on C3, AMD SEV-SNP on N2D) protect the node, not the pod; MAQPNA's per-pod attested tier needs Kata CoCo, which GKE does not offer managed.

flowchart LR
  subgraph GKE["GKE Standard cluster (one region)"]
    subgraph SYS["Default pool"]
      OP[operator]
      GW["gateway x3"]
      ID[identity broker]
    end
    subgraph GV["gvisor pool (GKE Sandbox)"]
      S0["tier-0 sandboxes"]
    end
    subgraph KT["kata pool (nested virt)"]
      S1["tier-1 sandboxes"]
    end
  end
  S0 -->|"Dataplane V2 default-deny"| GW
  S1 --> GW
  GW -->|"Workload Identity"| GCS[("GCS Bucket Lock<br/>audit ledger")]
  GW -->|"Workload Identity"| KMS["Cloud KMS / HSM<br/>checkpoint key"]
  GW --> PG[("Cloud SQL or<br/>CloudNativePG")]
  GW --> SEC["Google SecOps<br/>(optional SIEM)"]

Before you begin#

  • gcloud, kubectl, helm 3.14 or later.
  • The maqpna CLI and its maqpna-install plugin, version 2.1.0: curl -fsSL https://maqpna.com/install.sh | sh.
  • REGISTRY set to the release registry listed on maqpna.com/download.
REGION=europe-west3 CLUSTER=maqpna V=2.1.0
PROJECT_ID=$(gcloud config get-value project)
PROJECT_NUMBER=$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')
export REGISTRY=<release-registry-from-maqpna.com/download>

Step 1: Create the cluster#

Dataplane V2 (Cilium) enforces NetworkPolicy and GKE's FQDNNetworkPolicy. --workload-pool turns on Workload Identity Federation for GKE (on by default on Autopilot).

gcloud container clusters create "$CLUSTER" --region "$REGION" \
  --release-channel regular --enable-dataplane-v2 \
  --workload-pool="$PROJECT_ID.svc.id.goog" \
  --num-nodes 1 --machine-type e2-standard-4

Step 2: Add the sandbox node pools#

# tier-0: GKE Sandbox (gVisor)
gcloud container node-pools create gvisor --cluster "$CLUSTER" --region "$REGION" \
  --machine-type n2-standard-4 --image-type cos_containerd --sandbox type=gvisor --num-nodes 1

# tier-1 (optional): nested virtualization for Kata
gcloud container node-pools create kata --cluster "$CLUSTER" --region "$REGION" \
  --machine-type n2-standard-8 --image-type ubuntu_containerd \
  --enable-nested-virtualization --num-nodes 1 --node-labels maqpna.com/pool=kata

gcloud container clusters get-credentials "$CLUSTER" --region "$REGION"
kubectl get runtimeclass       # expect gvisor

For tier-1, install kata-deploy on the kata pool, then check that kata-qemu appears in kubectl get runtimeclass.

Step 3: Create the locked audit bucket#

The gateway requires a locked retention policy (Bucket Lock) or Object Retention Lock, and refuses to start without it. Locking is irreversible:

gcloud storage buckets create gs://acme-maqpna-audit --location="$REGION" --retention-period=3650d
gcloud storage buckets update gs://acme-maqpna-audit --lock-retention-period

Step 4: Grant the gateway its Google roles#

With Workload Identity Federation you grant IAM roles to the Kubernetes ServiceAccount directly, with no annotation:

MEMBER="principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$PROJECT_ID.svc.id.goog/subject/ns/maqpna-system/sa/maqpna-gateway"

for role in roles/storage.legacyBucketReader roles/storage.objectCreator roles/storage.objectViewer; do
  gcloud storage buckets add-iam-policy-binding gs://acme-maqpna-audit --role "$role" --member "$MEMBER"
done
gcloud kms keys add-iam-policy-binding maqpna-audit --keyring maqpna --location "$REGION" \
  --role roles/cloudkms.signerVerifier --member "$MEMBER"
gcloud kms keys add-iam-policy-binding maqpna-audit --keyring maqpna --location "$REGION" \
  --role roles/cloudkms.publicKeyViewer --member "$MEMBER"

The gateway reads the bucket's retention settings at startup (storage.buckets.get), uploads segments (storage.objects.create) and reads an object back to recognise an identical re-upload (storage.objects.get). A custom role with exactly those three permissions works too. Uploads use ifGenerationMatch=0, so the gateway never replaces an object. To impersonate a Google service account instead, set gateway.serviceAccount.annotations."iam.gke.io/gcp-service-account" and grant roles/iam.workloadIdentityUser. Details: Cloud identity & keys.

Step 5: Mirror the images (optional)#

Copy each image into Artifact Registry in the same region and verify it first:

AR=$REGION-docker.pkg.dev/$PROJECT_ID/maqpna
for img in maqpna-operator maqpna-gateway maqpna-identity maqpna-attest maqpna-attest-agent; do
  maqpna verify image "$REGISTRY/$img:$V"
  gcrane cp "$REGISTRY/$img:$V" "$AR/$img:$V"
done

The release's image-digests.txt lists every image. Set image.registry to the mirror and pin digests with image.digests.

Step 6: Install the agent-sandbox controller#

GKE Sandbox and GKE Agent Sandbox use the same upstream Sandbox objects that MAQPNA renders sessions into. If your cluster does not already serve them, install the controller:

kubectl get crd sandboxes.agents.x-k8s.io || \
  kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.6/sandbox-with-extensions.yaml

Step 7: Create the Secrets and your values file#

helm pull "oci://$REGISTRY/charts/maqpna" --version "$V" --untar
grep -n CHANGE-ME maqpna/values-production.yaml maqpna/values-gke.yaml

Create the Secrets as in Production install, step 1, then write my-values.yaml:

audit:
  sink: worm
  worm:
    provider: gcs
    bucket: acme-maqpna-audit
    credentials: gcp                 # keyless: Workload Identity Federation
  checkpointSigning:
    enabled: true
    keyURI: kms://gcp/projects/PROJECT_ID/locations/europe-west3/keyRings/maqpna/cryptoKeys/maqpna-audit/cryptoKeyVersions/1
networkPolicy:
  agentNamespaces:
    names: [team-a]

Cloud KMS keys of type EC_SIGN_P256_SHA256 and EC_SIGN_ED25519 both work.

Step 8: Validate, preflight and install#

maqpna values validate --chart ./maqpna --profile production \
  -f maqpna/values-production.yaml -f maqpna/values-gke.yaml -f my-values.yaml

maqpna preflight --chart ./maqpna --profile prod -f maqpna/values-gke.yaml -f my-values.yaml --strict

maqpna install --chart ./maqpna --profile prod -f maqpna/values-gke.yaml -f my-values.yaml \
  --license maqpna-license.jws --wait --timeout 15m

Step 9: Expose the gateway and filter egress#

Keep the chart's ClusterIP Service and add your own for an internal passthrough load balancer:

apiVersion: v1
kind: Service
metadata:
  name: maqpna-gateway-lb
  namespace: maqpna-system
  annotations:
    networking.gke.io/load-balancer-type: "Internal"
spec:
  type: LoadBalancer
  selector:
    app.kubernetes.io/name: maqpna-gateway
  ports:
    - {name: http, port: 443, targetPort: 8080, protocol: TCP}

Admit its traffic with a NetworkPolicy and choose TLS as in Ingress and TLS.

To mirror the gateway's egress allowlist into the kernel with FQDNNetworkPolicy, list your hosts in sovereignty.allowedEgressHosts, then:

networkPolicy:
  fqdnEgress:
    enabled: true
    provider: gke
    clusterCIDRs: ["<control-plane-endpoint>/32"]

On gke the gateway's egress becomes default-deny, so clusterCIDRs must list the API server endpoint; the chart refuses to render without it while gateway.configSync is on.

Step 10: Verify#

kubectl -n maqpna-system get pods
kubectl get trusttiers
helm test maqpna -n maqpna-system
kubectl -n maqpna-system port-forward svc/maqpna-gateway 9090:9090 &
maqpna doctor -n maqpna-system --gateway http://127.0.0.1:9090 --oidc-token-file admin.tok --strict
kubectl get pods -A -o custom-columns=NS:.metadata.namespace,POD:.metadata.name,RC:.spec.runtimeClassName,NODE:.spec.nodeName

A tier-0 sandbox pod shows gvisor and a node of the gvisor pool. Run hack/cloud-smoke.sh gke from the MAQPNA source to check keyless credentials, the bucket lock and egress blocking end to end.

Troubleshooting#

Symptom Cause Fix
Preflight runtimeclass-gvisor fails: belongs to Helm release or exists with handler The chart's gvisor class is on and clashes with GKE's Keep runtimeClasses.gvisor.enabled: false (the profile sets it).
tier-1 sessions stay Pending kata-deploy not installed on the kata pool Install it; kubectl get runtimeclass kata-qemu.
Gateway fails at startup naming the retention lock The bucket has no locked retention policy or Object Retention Lock Lock the retention policy (step 3).
GCS or KMS calls 403 The principal:// member does not match namespace or ServiceAccount The subject must be ns/maqpna-system/sa/maqpna-gateway.
Chart render fails with clusterCIDRs fqdnEgress on with provider gke and no API server CIDR Add the control-plane endpoint to networkPolicy.fqdnEgress.clusterCIDRs.
Gateway cannot reach the identity provider after turning on fqdnEgress The host is not in the allowlist Add it to sovereignty.allowedEgressHosts or networkPolicy.fqdnEgress.extraHosts.

More: Troubleshooting.

Next steps#