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,helm3.14 or later.- The
maqpnaCLI and itsmaqpna-installplugin, version 2.1.0:curl -fsSL https://maqpna.com/install.sh | sh. REGISTRYset 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#
- Production checklist
- Cloud identity & keys: Cloud KMS and
external_accountoutside GKE. - Cloud profiles: the SecOps SIEM sink and FQDN egress.
- Upgrade and rollback