Run the stack on Kubernetes
Install MAQPNA with Helm, run llamay as its in-cluster inference backend, point agents at it with provider llamay, and lock it down with network policy, secrets and sizing, with notes on what kosa8 does and does not do in a cluster.
This page takes the stack from your laptop to a Kubernetes cluster. The MAQPNA Helm chart can run
llamay for you as the gateway's private inference backend. Agents select it with
model.provider: llamay, and each session gets its own llamay inference context, as on the laptop.
kosa8 is different: it is a laptop and edge sandbox, not a Kubernetes runtime. In a cluster, agents run in MAQPNA's trust tiers instead. The table below says exactly what moves to the cluster.
| Capability | Laptop (maqpna dev) |
Cluster (Helm chart) |
|---|---|---|
| MAQPNA gateway, identity broker, audit ledger | Yes | Yes |
| llamay as the model backend, one context per session | Yes | Yes (llamay.enabled) |
| llamay context fork when a session forks | Yes | Yes |
| llamay audit anchored in the ledger | Yes | Yes, with a ReadWriteMany volume (llamay.audit.anchor.enabled) |
| Saving llamay contexts in a snapshot | Yes (maqpna dev snapshot) |
No. AgentSessionSnapshot does not save the llamay context yet. |
| kosa8 microVM sandboxes | Yes | No. kosa8 has no Kubernetes runtime (no containerd shim or RuntimeClass). Use tier-1 (Kata Containers with Firecracker microVM) for microVM isolation. |
| kosa8 audit cross-anchoring | Yes | No. The chart has no setting for it. |
flowchart LR
subgraph AG["Agent namespace (team-a)"]
S["Session sandbox<br/>tier-0 / tier-1 / tier-2"]
end
subgraph SYS["maqpna-system"]
GW["maqpna-gateway"]
IB["maqpna-identity"]
OP["maqpna-operator"]
LL["maqpna-llamay<br/>:8081"]
K[("Secret<br/>maqpna-llamay-key")]
A[("PVC maqpna-llamay-audit")]
end
S -->|"/llm/team-a.coder<br/>session token"| GW
GW -->|"Bearer key, per-session context"| LL
K -.->|"mounted read-only"| GW
K -.->|"LLAMAY_API_KEY"| LL
LL -->|"-audit -audit-strict"| A
A -.->|"read-only, verified"| GW
S --x|"blocked by NetworkPolicy"| LL
Before you begin#
- A Kubernetes cluster that meets MAQPNA's requirements, with a CNI that enforces NetworkPolicy
(Calico, Cilium and others). See Install on Kubernetes with Helm
and run
maqpna preflightfirst. - The
maqpnaCLI, Helm 3 andkubectl, with access to the cluster. - The MAQPNA chart and images. Set
REGISTRYto the release registry listed on maqpna.com/download. - A llamay image your nodes can pull. The chart's default is llamay's release image at tag
0.4.5(<tag>-cudafor GPU nodes). If your nodes cannot pull it, mirror it and setllamay.image.registryandllamay.image.repository. See llamay.com/download. - A model: a llamay store name or a GGUF file. Either the pod downloads it at start
(
llamay.models.pull), or you ship it in an image (llamay.models.bundle.image, for air-gapped clusters). - For audit anchoring: a StorageClass that supports
ReadWriteMany. See the platform tabs in Step 1. - For GPU inference: GPU nodes with the NVIDIA device plugin, so pods can request
nvidia.com/gpu.
Step 1: Write the values#
Save this as values-stack.yaml. It turns on llamay, pulls a model at start, anchors llamay's audit
and lets agents run in team-a.
llamay:
enabled: true
profile: cpu # cuda for GPU nodes
model: /models/qwen2.5-coder-7b-instruct-q4_k_m.gguf
models:
pull: hf:Qwen/Qwen2.5-Coder-7B-Instruct-GGUF/qwen2.5-coder-7b-instruct-q4_k_m.gguf
persistence:
size: 20Gi
concurrency: 2 # generations at once (llamay -concurrency)
resources:
requests: {cpu: "4", memory: 8Gi}
limits: {memory: 12Gi}
audit:
persistence:
accessModes: [ReadWriteMany] # required for anchor.enabled
storageClass: "" # an RWX class, see the tabs below
anchor:
enabled: true
intervalSeconds: 60
failClosed: true # take llamay out of routing while its chain does not verify
networkPolicy:
agentNamespaces:
names: [maqpna-agents, team-a]
The values you are most likely to change:
| Value | Default | What it does |
|---|---|---|
llamay.enabled |
false |
Renders the llamay Deployment, Service, PVCs, key Secret and NetworkPolicy, and the gateway's llamay config. |
llamay.profile |
cpu |
cpu or cuda. cuda uses the -cuda image tag, adds -gpu and requests llamay.gpu.count of llamay.gpu.resourceName. |
llamay.model |
"" |
Required. A GGUF path under /models, or a llamay store name. |
llamay.models.pull |
"" |
Download the model at start with llamay pull. Opens HTTPS egress for the llamay pod only. Refused with airgap.enabled. |
llamay.models.bundle.image |
"" |
An image whose bundle.path holds the model; copied into /models at start. Use it when air-gapped. |
llamay.port |
8081 |
The Service and container port. |
llamay.ctxMemBudget |
"" |
KV memory kept resident before idle contexts spill to disk (llamay -ctx-mem-budget, for example 8G). |
llamay.apiKey.existingSecret |
"" |
Your own Secret for the API key. Empty: the chart generates maqpna-llamay-key and keeps it across upgrades. |
llamay.audit.anchor.enabled |
false |
The gateway verifies llamay's audit chain and writes llamay/audit-anchor records into the ledger. Needs ReadWriteMany. |
llamay.nodeSelector, tolerations, affinity |
{}, [], {} |
Place llamay on GPU or large-memory nodes. |
All values are in the Helm values reference. The values that differ by platform are the RWX storage class and the GPU nodes:
llamay:
audit:
persistence:
accessModes: [ReadWriteMany]
storageClass: efs-sc # a StorageClass you created for the Amazon EFS CSI driver
# GPU: a node group with the EKS-optimised accelerated AMI and the NVIDIA device plugin
# nodeSelector: {node.kubernetes.io/instance-type: g5.xlarge}
EBS volumes are ReadWriteOnce only. Install the Amazon EFS CSI driver and create an EFS
StorageClass for the audit volume. See Install on EKS.
llamay:
audit:
persistence:
accessModes: [ReadWriteMany]
storageClass: azurefile-csi # built-in Azure Files class
# GPU: a GPU node pool with the NVIDIA device plugin
# tolerations: [{key: sku, operator: Equal, value: gpu, effect: NoSchedule}]
Azure Disk classes are ReadWriteOnce only. azurefile-csi and azurefile-csi-premium support
ReadWriteMany. See Install on AKS.
llamay:
audit:
persistence:
accessModes: [ReadWriteMany]
storageClass: standard-rwx # Filestore CSI driver
# GPU: a GPU node pool; GKE installs the driver and taints the nodes nvidia.com/gpu
Persistent Disk classes are ReadWriteOnce only. Enable the Filestore CSI driver to get the
standard-rwx and premium-rwx classes. Filestore has a large minimum size, so check the cost.
See Install on GKE.
If you cannot get ReadWriteMany storage, set llamay.audit.anchor.enabled: false and keep
accessModes: [ReadWriteOnce]. llamay still writes its audit and stops serving if a record cannot be
written. You then verify the chain yourself with maqpna llamay verify-audit, and the ledger has no
llamay/audit-anchor records.
Step 2: Validate the values#
maqpna values validate --profile production -f values-stack.yaml
The command checks the values offline against the chart's schema and production rules, and exits with code 3 when something is wrong. The chart itself refuses these combinations when it renders:
| Message | Fix |
|---|---|
llamay.enabled requires llamay.model (a GGUF path under /models or a llamay store name) |
Set llamay.model. |
llamay.profile must be cpu or cuda |
Use one of the two. |
set llamay.models.pull or llamay.models.bundle.image, not both |
Keep one. |
llamay.models.pull downloads from the internet; with airgap.enabled use llamay.models.bundle.image |
Ship the model as a bundle image. |
llamay.audit.anchor.enabled requires llamay.audit.persistence.accessModes to include ReadWriteMany ... |
Use an RWX class, or turn anchoring off. |
Step 3: Install or upgrade#
maqpna preflight --profile prod -f values-stack.yaml
maqpna install --profile prod -f values-stack.yaml \
--chart "oci://$REGISTRY/charts/maqpna" --version 2.1.0 \
--set image.registry="$REGISTRY" --wait --timeout 15m
helm pull "oci://$REGISTRY/charts/maqpna" --version 2.1.0 --untar
helm upgrade --install maqpna ./maqpna \
--namespace maqpna-system --create-namespace \
--set image.registry="$REGISTRY" \
-f values-stack.yaml --wait --timeout 15m
With release name maqpna in maqpna-system, the chart creates:
| Object | Name | Notes |
|---|---|---|
| Deployment | maqpna-llamay |
One replica, Recreate strategy, read-only root file system. Runs llamay serve -addr 0.0.0.0:8081 -m <model> -audit /var/lib/llamay/audit/audit.jsonl -audit-strict -audit-sync. |
| Service | maqpna-llamay |
ClusterIP, port 8081. |
| Secret | maqpna-llamay-key |
The API key. Annotated helm.sh/resource-policy: keep. |
| PVCs | maqpna-llamay-models, maqpna-llamay-audit |
Both kept when you uninstall. The audit is evidence. |
| NetworkPolicy | maqpna-llamay |
Ingress from gateway pods only. No egress, except DNS and HTTPS while models.pull is set. |
| Gateway config | llamay block in gateway.json |
upstream: http://maqpna-llamay.maqpna-system.svc:8081/v1, apiKeyFile: /var/run/maqpna-llamay/api-key, and with anchoring, auditAnchors for /var/lib/llamay-audit/audit.jsonl. |
Check it worked.
kubectl -n maqpna-system rollout status deploy/maqpna-llamay --timeout 15m
kubectl -n maqpna-system logs deploy/maqpna-llamay -c pull-model # only with models.pull
maqpna status -n maqpna-system
maqpna doctor -n maqpna-system
The first start downloads the model, so it can take several minutes. The startup probe allows llamay up to 10 minutes to load a model before Kubernetes restarts it.
Step 4: Point an agent at llamay#
Create the agent and its policy in team-a:
# agent.yaml
apiVersion: maqpna.com/v1alpha1
kind: Agent
metadata:
name: coder
namespace: team-a
spec:
image: registry.example.eu/agents/coder@sha256:<digest>
tier: tier-1 # Kata + Firecracker microVM; tier-0 is gVisor
model:
provider: llamay
endpoint: http://maqpna-llamay.maqpna-system.svc:8081 # "/v1" is appended
name: qwen2.5-coder-7b-instruct-q4_k_m
jurisdiction: EU-DE
tokensPerMinute: 60000
maxTokensPerRequest: 4096
tools:
- name: echo # the chart's example MCP server
maxSessionTTL: 4h
---
apiVersion: maqpna.com/v1alpha1
kind: ToolPolicy
metadata:
name: coder-policy
namespace: team-a
spec:
agents: ["coder"]
defaultAction: deny
rules:
- name: echo-allowed
servers: [echo]
tools: ["echo"]
action: allow
maqpna validate -f agent.yaml --strict
maqpna apply -f agent.yaml -n team-a
maqpna rollout status ag/coder -n team-a
What provider: llamay does:
- The operator writes the route
team-a.coderwithprovider: llamay. The sandbox getsMAQPNA_MODEL_ENDPOINT={gateway}/llm/team-a.coder/v1. It never learns llamay's address. - The endpoint is on the origin of the chart's llamay, so the gateway presents the chart's key. The
agent needs no
apiKeySecretRef. - The gateway gives each session its own llamay context and forks it when the session forks
(
maqpna session fork). It strips anyllamay_sessionthe agent sends.
The trust tier must exist and its runtime must be installed on the nodes: maqpna tier list. The
chart's sample tiers are tier-0 (gVisor), tier-1 (Kata with Firecracker) and tier-2
(confidential VM).
A llamay outside the chart#
To use a llamay you run yourself, for example on a GPU server outside the cluster, leave
llamay.enabled off and give the agent the key:
kubectl -n team-a create secret generic llamay-gpu --from-literal=api-key='<your-llamay-api-key>'
spec:
model:
provider: llamay
endpoint: https://llamay-gpu.internal.example.eu:11435
name: qwen2.5-coder-32b-instruct-q4_k_m
jurisdiction: EU-DE
apiKeySecretRef: {name: llamay-gpu, key: api-key}
The operator copies the key into a gateway-only Secret. It never reaches the sandbox. Per-session
contexts and context fork work the same. Audit anchoring does not: the gateway can anchor only an
audit file it can read, and the chart mounts only its own llamay's audit volume. If you restrict the
gateway's egress (networkPolicy.fqdnEgress, or the sovereignty policy's allowed hosts), add this
host there.
Step 5: Verify#
-
Admission. Create a session and wait for it to run:
maqpna smoke --sandbox team-a/coder maqpna session start --agent coder -n team-a --task "Say hello" --wait -
The model call is governed. Follow the ledger while the agent works. You need an admin or auditor login (
maqpna login):kubectl -n maqpna-system port-forward svc/maqpna-gateway 8080:8080 & GW=http://127.0.0.1:8080 maqpna audit tail --gateway "$GW" -n team-a --agent coder -fEach model call is a record on route
team-a.coder. Export one session and look for the llamay context:maqpna audit export --gateway "$GW" --session <session-name> --namespace team-a --out session.jsonl grep -c llamayContext session.jsonl -
The audit chain is anchored. With anchoring on, the gateway exports these metrics:
curl -s "$GW/metrics" | grep -E 'maqpna_gateway_llamay_(audit_verified|audit_head_seq|contexts)'maqpna_gateway_llamay_audit_verified{anchor="in-cluster"}is1when the chain verifies. If you setgateway.config.adminListen, scrape/metricson the admin port instead. -
Only the gateway can reach llamay. A pod that is not the gateway times out:
kubectl -n maqpna-system run np-check --rm -it --restart=Never --image=curlimages/curl -- \ curl -sS -m 5 http://maqpna-llamay:8081/healthzThe expected result is a timeout. A JSON answer means your CNI does not enforce NetworkPolicy.
Network policy#
The chart renders three layers. Together they keep llamay private to the gateway:
| Policy | Rendered when | Effect |
|---|---|---|
| Agent namespaces | networkPolicy.agentNamespaces.enabled (default on) |
Default deny in each listed namespace: no ingress, egress only to DNS and the MAQPNA gateway. Per-session policies from the operator add the same. |
maqpna-llamay |
networkPolicy.controlPlane.enabled (default on) |
Ingress only from pods labelled app.kubernetes.io/name: maqpna-gateway, on the llamay port. Egress: none, or DNS and TCP 443 while models.pull is set. |
| Gateway FQDN egress | networkPolicy.fqdnEgress.enabled |
The CNI enforces the gateway's egress allow-list (Cilium, GKE Dataplane V2, EKS VPC CNI). In-cluster names such as the llamay Service are not affected. |
Keep the gateway's label app.kubernetes.io/name: maqpna-gateway (gateway.name). Both the llamay
policy and the operator's per-session policies select it.
Secrets#
| Secret | Who reads it | How |
|---|---|---|
maqpna-llamay-key (or llamay.apiKey.existingSecret) |
llamay | LLAMAY_API_KEY environment variable, never argv |
| the same Secret | the gateway | Mounted read-only at /var/run/maqpna-llamay/api-key, mode 0400, re-read on every call |
apiKeySecretRef of an Agent (external llamay) |
the gateway | Copied by the operator into the gateway-only maqpna-model-credentials |
Sandboxes never get a llamay key. To bring your own key:
kubectl -n maqpna-system create secret generic llamay-api-key --from-literal=api-key="$(openssl rand -hex 32)"
llamay:
apiKey:
existingSecret: llamay-api-key
key: api-key
To rotate the key, update the Secret, then restart llamay so it reads the new value:
kubectl -n maqpna-system rollout restart deploy/maqpna-llamay
The gateway picks up the new key from the mounted file once the kubelet refreshes it, which can take
a minute. Model calls in that window answer 401 from llamay and fail over, if the route has
fallbacks. The restart also drops all llamay contexts: the next call of each session costs one
prefill, nothing more.
Scaling and sizing#
- llamay runs one replica. Contexts live in the llamay process, and the audit chain has one
writer. Scale up, not out: more CPU or a GPU (
profile: cuda),concurrencyfor parallel generations,ctxMemBudgetto spill idle contexts to disk. - Size memory for the model plus contexts. The model is mapped into memory, and each active
context holds KV state. Start with a memory limit of about twice the GGUF file size and watch llamay's
own
/metrics(llamay_kv_bytes,llamay_sessions). - Gateway replicas. More than one gateway replica needs
state.backend: postgres. Context bindings live in each gateway process, so a session that lands on another replica gets a fresh context (correct, but one prefill slower), and fork lineage is per replica. - Idle contexts. The gateway drops a session's context after
contextIdleSeconds(default 1800) and keeps at mostmaxContexts(default 4096). Tune them ingateway.config.llamay; keys you set there take precedence over the chart's. - Restarts. The
Recreatestrategy means a short gap in service during upgrades. With fallbacks inAgent.spec.model.fallbacks, calls fail over to another endpoint meanwhile.
Air-gapped clusters#
- Mirror the llamay image into your registry with the MAQPNA images. With
airgap.imageRegistryset, the chart pulls llamay from that registry, under the same repository path. - Ship the model as an image and set
llamay.models.bundle.image(andbundle.path, default/bundle). The image needs/bin/shandcp. airgap.enabledrefusesmodels.pull. In a government deployment, use a llamay sovereign build image, which also disables network model sources.
See Air-gapped install.
What can go wrong#
| Symptom | Cause | Fix |
|---|---|---|
maqpna-llamay pod Pending |
No node has the requested memory or GPU, or the RWX PVC cannot bind. | kubectl -n maqpna-system describe pod -l app.kubernetes.io/name=maqpna-llamay. Check the StorageClass and GPU nodes. |
ImagePullBackOff on llamay |
The nodes cannot pull the default llamay image. | Mirror it and set llamay.image.registry and llamay.image.repository. |
pull-model init container fails |
No HTTPS egress, or the model reference is wrong. | Check its logs. Check that your CNI allows the llamay pod DNS and TCP 443. |
Model calls answer 503 llamay_audit_unverified |
The anchored audit chain is broken, missing or rewritten, and failClosed is on. |
Check maqpna_gateway_llamay_audit_verified and the llamay/audit-unverified record. See stack troubleshooting. |
Model calls answer 401 |
The key was rotated and llamay was not restarted, or the reverse. | Restart deploy/maqpna-llamay, wait for the gateway's mounted Secret to refresh. |
Agent Ready=False, TierFound=False |
spec.tier names a tier that does not exist. |
maqpna tier list. |
Sessions never get a context (maqpna_gateway_llamay_contexts stays 0) |
The Agent's endpoint is not the chart's Service and provider is not llamay. |
Set model.provider: llamay. |
Next steps#
- Day-to-day development with the stack
- llamay reference: every gateway, Helm and Agent setting
- Security model of the stack
- Scaling and HA
- Production checklist