Install on Amazon EKS
Install MAQPNA on Amazon EKS step by step, from the cluster and sandbox node groups to EKS Pod Identity, S3 Object Lock audit storage and a passing doctor check.
This page installs MAQPNA on Amazon Elastic Kubernetes Service (EKS). You create the cluster, add node groups for the
sandbox runtimes, turn on NetworkPolicy, wire EKS Pod Identity to the gateway, and install the chart with the
values-eks.yaml profile on top of the production profile. It ends with maqpna smoke and maqpna doctor.
The generic steps (Secrets, values, OIDC, the PostgreSQL state backend) are in Production install with Helm. This page links to them and adds what is specific to EKS.
What you get#
| Trust tier | Runtime on EKS | Node group | RuntimeClass |
|---|---|---|---|
| tier-0 (gVisor) | runsc, installed by your launch template or AMI |
managed node group labelled maqpna.com/pool=gvisor |
gvisor (created by the chart) |
| tier-1 (microVM) | Kata Containers from kata-deploy | C8i, M8i or R8i with EC2 nested virtualization, or any *.metal type |
kata-qemu (created by kata-deploy) |
| tier-2 (confidential VM) | not available on EKS | — | not created |
Fargate cannot host sandboxes: it has no custom RuntimeClasses and no DaemonSets. Whether EKS Auto Mode nodes accept
a custom runsc or Kata runtime is not verified; use managed node groups (or Karpenter with a custom AMI) for the
sandbox pools.
flowchart LR
subgraph VPC["Your VPC (one region)"]
subgraph SYS["System node group"]
OP[operator]
GW["gateway x3"]
ID[identity broker]
end
subgraph GV["gVisor node group"]
S0["tier-0 sandboxes"]
end
subgraph KT["Kata node group (C8i/M8i/R8i)"]
S1["tier-1 sandboxes"]
end
PG[("PostgreSQL<br/>RDS or CloudNativePG")]
end
S0 -->|"default-deny, gateway only"| GW
S1 --> GW
GW --> PG
GW -->|"Pod Identity"| S3[("S3 Object Lock<br/>audit ledger")]
GW -->|"Pod Identity"| KMS["AWS KMS<br/>checkpoint key"]
GW --> CW["CloudWatch Logs<br/>(optional SIEM)"]
Before you begin#
awsCLI v2,eksctl,kubectl,helm3.14 or later, andcraneif you mirror images.- 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.- An AWS region in the jurisdiction you need. The examples use
eu-central-1.
export AWS_REGION=eu-central-1 CLUSTER=maqpna V=2.1.0
export REGISTRY=<release-registry-from-maqpna.com/download>
Step 1: Create the cluster#
Create the cluster with a system node group for the MAQPNA control plane (operator, gateway, identity broker):
eksctl create cluster --name "$CLUSTER" --region "$AWS_REGION" --version 1.33 \
--nodegroup-name system --node-type m7i.xlarge --nodes 3 --node-ami-family AmazonLinux2023
The chart needs Kubernetes 1.29 or later (1.30 or later when principalBinding is on, as in the production profile).
Step 2: Turn on NetworkPolicy and Pod Identity#
Sandboxes are default-deny: they may reach only DNS and the gateway. The VPC CNI enforces this once its network policy agent is on. Pod Identity gives the gateway AWS credentials without a key in a Secret.
aws eks update-addon --cluster-name "$CLUSTER" --addon-name vpc-cni \
--configuration-values '{"enableNetworkPolicy":"true"}'
aws eks create-addon --cluster-name "$CLUSTER" --addon-name eks-pod-identity-agent
Cilium or Calico also work instead of the VPC CNI policy agent.
Step 3: Add the gVisor node group (tier-0)#
EKS ships no gVisor. Add a managed node group whose launch template user data installs runsc and registers it with
containerd. Recent gVisor releases ship one archive per architecture; it also holds a gvisor-bin/ directory that
must sit next to runsc:
ARCH=$(uname -m); URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
curl -fsSLO "${URL}/gvisor.tar.bz2" -O "${URL}/gvisor.tar.bz2.sha512"
sha512sum -c gvisor.tar.bz2.sha512
tar -xjf gvisor.tar.bz2
install -m 0755 runsc containerd-shim-runsc-v1 /usr/local/bin/
cp -r gvisor-bin /usr/local/bin/
cat >>/etc/containerd/config.toml <<'EOF'
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
EOF
systemctl restart containerd
Label the node group maqpna.com/pool=gvisor. values-eks.yaml pins the chart's gvisor RuntimeClass to that label
(runtimeClasses.gvisor.nodeSelector). Taint the group as well if only sandboxes should run there.
Step 4: Add the Kata node group (tier-1, optional)#
EC2 offers nested virtualization (KVM) on virtual C8i, M8i and R8i instances, so Kata no longer needs bare metal.
Create a node group of one of those types, for example m8i.2xlarge, with nested virtualization turned on in its
launch template (check the EC2 user guide for the current option name and the supported sizes). *.metal instances
still work.
Then install Kata with kata-deploy
on that group. kata-deploy creates RuntimeClasses such as kata-qemu and kata-fc. The profile maps tier-1 to
kata-qemu and keeps the chart's own kata-fc class off (kata-fc also needs the devmapper snapshotter).
kubectl get runtimeclass # expect kata-qemu once kata-deploy has finished
Step 5: Create the audit storage class and bucket#
The profile puts the gateway's local ledger mirror on a persistent gp3 volume (gateway.auditStorage). Install the
EBS CSI driver add-on (it needs an IAM role, see the AWS documentation) and create the class if your cluster has none:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3
provisioner: ebs.csi.aws.com # EKS Auto Mode: ebs.csi.eks.amazonaws.com
parameters: {type: gp3}
volumeBindingMode: WaitForFirstConsumer
Create an S3 bucket with Object Lock in the same region for write-once audit storage (WORM):
aws s3api create-bucket --bucket acme-maqpna-audit --region "$AWS_REGION" \
--create-bucket-configuration LocationConstraint="$AWS_REGION" --object-lock-enabled-for-bucket
The gateway checks at startup that Object Lock is on, and does not start if it is not.
Step 6: Give the gateway an AWS identity#
Create an IAM role for the gateway with s3:PutObject, s3:PutObjectRetention and s3:GetObject on the bucket,
s3:GetBucketObjectLockConfiguration (the startup Object Lock check), and
kms:Sign and kms:GetPublicKey on an ECC_NIST_P256 key if you keep the checkpoint key in AWS KMS. Associate it
with the gateway's ServiceAccount:
aws eks create-pod-identity-association --cluster-name "$CLUSTER" \
--namespace maqpna-system --service-account maqpna-gateway \
--role-arn arn:aws:iam::111122223333:role/maqpna-gateway
Pod Identity needs no chart change. Use IRSA instead only where Pod Identity is not supported, or for AWS Marketplace
metering: set gateway.serviceAccount.annotations."eks.amazonaws.com/role-arn". Do not configure both for one
component; IRSA wins. Details, partitions and KMS key URIs: Cloud identity & keys.
Step 7: Install the agent-sandbox controller#
MAQPNA creates sandboxes through the upstream agent-sandbox project. The chart does not install it.
kubectl apply -f https://github.com/kubernetes-sigs/agent-sandbox/releases/download/v1.0.6/sandbox-with-extensions.yaml
Step 8: Mirror the images (optional)#
Connected clusters pull from the release registry. To keep images in your region, copy each one to ECR and verify it first:
ECR=111122223333.dkr.ecr.$AWS_REGION.amazonaws.com/maqpna
for img in maqpna-operator maqpna-gateway maqpna-identity maqpna-attest maqpna-attest-agent; do
maqpna verify image "$REGISTRY/$img:$V"
aws ecr create-repository --repository-name "maqpna/$img" >/dev/null
crane copy "$REGISTRY/$img:$V" "$ECR/$img:$V"
done
The release's image-digests.txt lists every image. Point the chart at the mirror with image.registry: <ECR> and pin
each image with image.digests (Verify releases).
Step 9: Create the Secrets and your values file#
Pull the chart; it carries every profile file:
helm pull "oci://$REGISTRY/charts/maqpna" --version "$V" --untar
grep -n CHANGE-ME maqpna/values-production.yaml maqpna/values-eks.yaml
Create the Secrets the production profile expects (state DSN, identity key, audit signing key, WORM, Trustee) as in
Production install, step 1. With
keyless S3 you do not need the maqpna-audit-s3 Secret.
Then write my-values.yaml with the EKS-specific settings:
gateway:
cloudCredentials:
aws: {region: eu-central-1} # Pod Identity does not inject AWS_REGION
audit:
sink: worm
worm:
provider: s3
endpoint: https://s3.eu-central-1.amazonaws.com
region: eu-central-1
bucket: acme-maqpna-audit
credentials: aws # keyless: no credentialsSecret
checkpointSigning:
enabled: true
keyURI: kms://aws/alias/maqpna-audit
sovereignty:
jurisdiction: EU-DE
networkPolicy:
agentNamespaces:
names: [team-a]
The production profile has no tier-2 on EKS, so leave attestation.enabled as you need it; with no attested tier you
can set it to false.
Step 10: Validate, preflight and install#
maqpna values validate --chart ./maqpna --profile production \
-f maqpna/values-production.yaml -f maqpna/values-eks.yaml -f my-values.yaml
maqpna preflight --chart ./maqpna --profile prod -f maqpna/values-eks.yaml -f my-values.yaml --strict
maqpna install --chart ./maqpna --profile prod -f maqpna/values-eks.yaml -f my-values.yaml \
--license maqpna-license.jws --wait --timeout 15m
--profile prod loads values-production.yaml from the chart at the lowest priority; your -f files override it.
The preflight runtimeclass-gvisor check fails until a node carries maqpna.com/pool=gvisor.
Step 11: Expose the gateway#
The chart creates a ClusterIP Service (maqpna-gateway, port 8080) and no Ingress. Agents reach it inside the
cluster. For people (console, approvals) and external MCP clients, add an internal load balancer or an Ingress with
the AWS Load Balancer Controller, and a NetworkPolicy that admits it. The steps, TLS options and the policy are in
Ingress and TLS.
Keep the chart's Service as ClusterIP: with gateway.service.type: LoadBalancer the chart leaves the admin port
(adminListen) off the Service. Add a second Service of your own for the load balancer instead:
apiVersion: v1
kind: Service
metadata:
name: maqpna-gateway-lb
namespace: maqpna-system
annotations:
service.beta.kubernetes.io/aws-load-balancer-scheme: internal
spec:
type: LoadBalancer
selector:
app.kubernetes.io/name: maqpna-gateway
ports:
- {name: http, port: 443, targetPort: 8080, protocol: TCP}
Traffic from a load balancer arrives from outside the cluster, so also add a NetworkPolicy with an ipBlock for
your VPC or load balancer subnets on port 8080.
Step 12: Verify#
kubectl -n maqpna-system get pods
kubectl get trusttiers
helm test maqpna -n maqpna-system # runs maqpna smoke in a pod
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
admin.tok holds an access token from your identity provider with the auditor or admin role. Then start one
tier-0 session and check where it landed:
kubectl get pods -A -o custom-columns=NS:.metadata.namespace,POD:.metadata.name,RC:.spec.runtimeClassName,NODE:.spec.nodeName
The sandbox pod must show gvisor and a node of the gVisor group. To prove cloud credentials, WORM immutability and
egress blocking end to end, run hack/cloud-smoke.sh eks from the MAQPNA source
(Cloud profiles).
Troubleshooting#
| Symptom | Cause | Fix |
|---|---|---|
Preflight runtimeclass-gvisor fails: 0 node(s) match nodeSelector |
The gVisor node group is not labelled | Label it maqpna.com/pool=gvisor, or change runtimeClasses.gvisor.nodeSelector. |
tier-0 sandbox pod ContainerCreating, event no runtime for "runsc" |
runsc is not registered with containerd on that node |
Check the launch template user data and systemctl status containerd on the node. |
tier-1 sessions stay Pending |
kata-deploy is not installed, or the instance has no nested virtualization | kubectl get runtimeclass kata-qemu; check the launch template and instance type. |
| Gateway does not start, log names Object Lock | The bucket was created without Object Lock | Object Lock is set at bucket creation; create a new bucket with --object-lock-enabled-for-bucket. |
WORM or KMS calls fail with AccessDenied |
Missing association, wrong ServiceAccount, or both IRSA and Pod Identity set | aws eks list-pod-identity-associations --cluster-name "$CLUSTER"; use one mechanism per component. |
| KMS signing fails with an unknown region | Pod Identity does not inject AWS_REGION |
Set gateway.cloudCredentials.aws.region, or use a key ARN that names the region. |
| Sandboxes reach the internet | NetworkPolicy is not enforced | Turn on enableNetworkPolicy on the vpc-cni add-on, or install Cilium or Calico. |
Audit PVC Pending |
No gp3 StorageClass or no EBS CSI driver |
Create the class from step 5 and install the aws-ebs-csi-driver add-on. |
More: Troubleshooting.
Next steps#
- Production checklist: go through every item before agents act on real systems.
- Cloud identity & keys: KMS signing keys and token exchange to AWS credentials.
- Monitoring and alerts: ServiceMonitor, alerts and dashboards.
- Upgrade and rollback: upgrade with
maqpna upgrade.