MAQPNADocs

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#

  • aws CLI v2, eksctl, kubectl, helm 3.14 or later, and crane if you mirror images.
  • 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.
  • 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#