emptyDir event spool works on any Kubernetes distribution. Only the certificate-delivery path in this guide (AWS Secrets Manager + the Secrets Store CSI Driver + IRSA) is specific to AWS / EKS. If you run elsewhere, keep the pod and spool layout and substitute your platform’s secret-mount mechanism for Phases 2 and 3.
When to use this pattern. Pick single-pod when your application should not call across a network boundary to reach the collector (low-latency in-pod IPC, tight lifecycle coupling, per-tenant pod isolation). For multi-app fleets that share one collector per node or per cluster, see enterprise-docs/kubernetes-deployment.md instead.
At a glance
- Events (in-pod): your app’s SDK writes
.jsonlfiles into the sharedemptyDirat$AGENTEYE_HOME/events/; the collector sweeper reads them and uploads. No localhost port, no loopback, pure shared-filesystem handoff. - mTLS cert (pod ← cloud): the Secrets Store CSI Driver mounts the cert bundle from Secrets Manager into a read-only volume at
/etc/agenteye/tls/, scoped to the collector container.
| Party | Responsibility |
|---|---|
| Exosphere | Issues the mTLS client certificate and delivers the bundle into your AWS account’s Secrets Manager under a stable name. Re-publishes the renewed bundle into the same secret before expiry. |
| You | Install the Secrets Store CSI Driver, grant the pod’s ServiceAccount read access to the secret via IRSA, and apply the Pod manifest. That’s it. |
Prerequisites
In your AWS account / EKS cluster
-
An EKS cluster with an OIDC provider associated. Confirm with:
If the command returns an
https://oidc.eks.…URL, OIDC is enabled. If not, associate one: - The Secrets Store CSI Driver and the AWS provider installed in the cluster (see § Phase 2).
-
AWS CLI v2 and
kubectlon your workstation.
Coordination with Exosphere
Before you deploy, Exosphere delivers the mTLS client bundle into your AWS account’s Secrets Manager and provides:- The secret name (convention:
agenteye/mtls-client/<your-cluster>) - The AWS region the secret lives in
- The AgentEye backend URL to configure the collector with
- Your collector API key (see enterprise-docs/api-keys.md)
Phase 1: What Exosphere delivers
You do not generate the mTLS client certificate yourself. Exosphere issues it and delivers the bundle directly into your AWS account’s Secrets Manager, so the only credential material that ever lands in your environment is the finished, ready-to-mount secret. What arrives in your account:| Property | Value |
|---|---|
| Secret name | agenteye/mtls-client/<cluster-name> (stable across renewals) |
| Region | The AWS region you nominated for your EKS cluster |
| Payload | A single JSON secret with three keys (client.crt, client.key, and ca.crt), each holding the PEM-encoded material |
| Tag | AgentEyeCluster=<cluster-name> |
SecretProviderClass and IAM policy keep working untouched. For the certificate lifecycle (validity, renewal cadence, expiry alerting) see enterprise-docs/kubernetes-deployment.md.
Phase 2: Install the Secrets Store CSI Driver + AWS provider
Skip this step if you already run another workload that mounts AWS secrets via CSI.Running for every pod.
Why rotationPollInterval=1h? When Exosphere publishes a renewed certificate, Secrets Manager is updated in place. The CSI Driver re-reads the secret on this interval and re-writes the mounted files. The collector reads the certificate files once at startup, so it begins presenting the renewed certificate only after a process restart; see § Certificate rotation for how to trigger one.
Phase 3: Grant the pod read access to the secret (IRSA)
3.1 Create the IAM policy
Save asagenteye-mtls-reader-policy.json:
<region>, <account-id>, and <cluster-name>. The trailing -* matches the six-character random suffix AWS appends to every secret ARN.
Create the policy:
3.2 Create the IAM role and bind it to the pod’s ServiceAccount
ServiceAccount named agenteye-pod with the eks.amazonaws.com/role-arn annotation pointing at the new role.
3.3 Required IAM permissions: summary
| Permission | Scope | Why |
|---|---|---|
secretsmanager:GetSecretValue | arn:aws:secretsmanager:<region>:<acct>:secret:agenteye/mtls-client/<cluster>-* | CSI Driver reads the cert bundle on every mount + rotation tick. |
secretsmanager:DescribeSecret | same | CSI Driver calls DescribeSecret to detect version changes between polls. |
secretsmanager:PutSecretValue, secretsmanager:UpdateSecret, or secretsmanager:DeleteSecret to the pod. The pod only ever reads the secret; writing new versions into it is handled by Exosphere when the certificate is issued or renewed.
If the secret is encrypted with a customer-managed KMS key (not the default aws/secretsmanager key), also grant:
Phase 4: Deploy the Pod
4.1 SecretProviderClass
agenteye-mtls-spc.yaml:
jmesPath block tells the AWS provider to split the JSON secret into three separate files on disk. The quoting in '"client.crt"' is required because JMESPath treats . as a sub-expression operator.
4.2 Pod / Deployment manifest
How the two containers talk to each other. The AgentEye SDK and the collector do not communicate over a network socket; there is no local HTTP port. The SDK writes event batches as.jsonl files into $AGENTEYE_HOME/events/, and the collector continuously watches that directory and uploads each file. For a sidecar pod this means:
- Both containers mount the same
emptyDirvolume at the same path. - Both containers set
AGENTEYE_HOMEto that path. - Your application image must have the AgentEye SDK installed and configured (see enterprise-docs/python-sdk.md).
WhenAGENTEYE_HOMEis unset, both the SDK and the collector default to~/.agenteye, and the two containers have different home directories, so they would land on two separate spools and the handoff would silently fail. SetAGENTEYE_HOMEto the same explicit path on both containers. The §4.3 verification and the matching Troubleshooting row catch this if it is missed.
agenteye-pod.yaml (Deployment with one replica, scale as needed):
agenteye-collector-api-key Secret holds the collector’s API key (see enterprise-docs/api-keys.md for provisioning).
Apply:
4.3 Verify
client.crt, client.key, ca.crt all present and read-only, owned by the container user.
Confirm the shared event spool is visible to both containers:
AGENTEYE_HOME differs); see § Troubleshooting.
End-to-end smoke test:
Done: N/N uploaded, 0 failed. summary. If the spool is empty it prints No pending files. and exits without validating anything — so run this only after your app has flushed at least one event.
Note that flush exits non-zero only for local setup faults: missing configuration (no URL/key resolved) or an unreadable/unparseable TLS cert (check § Troubleshooting). A wrong API key does not change the exit code — the upload gets a 401, the file is moved to failed/, and the command still prints [FAILED] … per file plus Done: 0/N uploaded, N failed. and exits 0. To detect a bad key or a rejected upload, read the Done:/[FAILED] output or check for files landing in $AGENTEYE_HOME/failed/, not the exit code.
Certificate rotation
The client certificate is valid for 90 days and is renewed automatically about 15 days before expiry; Exosphere then publishes the renewed bundle into the same Secrets Manager secret. From there, the in-pod flow is:-
Secrets Manager’s secret gets a new
AWSCURRENTversion. ARN and name are unchanged. -
Within
rotationPollInterval(1h by default; see § Phase 2), the CSI Driver reads the new version and rewrites the files under/etc/agenteye/tls/. -
The collector loads the certificate files once at startup, so it keeps presenting the previous certificate until the process restarts. To switch to the renewed material, restart the collector; a rolling restart is sufficient:
To make this automatic, add a sidecar that watches
/etc/agenteye/tls/(for example withinotifywait) and triggers the rollout when the files change.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Pod stuck in ContainerCreating, events show MountVolume.SetUp failed for volume "agenteye-mtls" | CSI provider can’t reach Secrets Manager | Check IRSA is correctly bound: kubectl describe sa agenteye-pod -n <ns> shows the eks.amazonaws.com/role-arn annotation. Check CloudTrail for the AssumeRole call. |
Error: AccessDeniedException: not authorized to perform secretsmanager:GetSecretValue | IAM policy is scoped to the wrong ARN | The secret ARN suffix is random; use agenteye/mtls-client/<cluster>-* with the wildcard, not the exact ARN. |
Error: ParameterNotFound from AWS provider | Secret name mismatch between SecretProviderClass.objects[].objectName and the secret Exosphere delivered | Confirm the exact name with aws secretsmanager list-secrets --filters Key=tag-key,Values=AgentEyeCluster. |
jmesPath error, only one file mounted | JMESPath syntax | The dots in the JSON keys require double-quoting: '"client.crt"', not client.crt. |
Collector logs tls: bad certificate after a renewal | The CSI Driver hasn’t polled the new version yet, or the collector is still running with the previous certificate it loaded at startup | Confirm the mounted files have updated (ls -l /etc/agenteye/tls/), then restart the collector to load them: kubectl rollout restart deploy/my-app-with-collector -n <ns>. See § Certificate rotation. |
Collector container crashloops with no such file or directory: /etc/agenteye/tls/client.crt | Volume not yet populated on first start; startup probe too aggressive | Add a small initial delay or use an init container that waits for the file to exist: until [ -f /etc/agenteye/tls/client.crt ]; do sleep 1; done. |
CSI Driver pod OOMKilled | Default memory limits too low for clusters with many SecretProviderClasses | Bump --set linux.resources.limits.memory=200Mi in the Helm install. |
App runs cleanly, agenteye-collector flush reports No pending files., but your AgentEye dashboard shows no events | The app and collector are not sharing the event spool | Check that (a) both containers mount the same agenteye-spool emptyDir at the same path, and (b) both set AGENTEYE_HOME to that path. Run the two ls /var/lib/agenteye/ checks from § 4.3; the listings must match. |
Reference: files on disk in the pod
The pod has two data paths on disk:mTLS cert bundle: /etc/agenteye/tls/ (CSI, read-only, collector only)
Mounted by the Secrets Store CSI Driver from AWS Secrets Manager.
| File | Contents | Used by collector as |
|---|---|---|
client.crt | PEM-encoded client certificate | AGENTEYE_TLS_CERT |
client.key | PEM-encoded private key | AGENTEYE_TLS_KEY |
ca.crt | PEM-encoded CA cert | AGENTEYE_TLS_CA (optional, only when the AgentEye server cert isn’t publicly-trusted) |
Event spool: $AGENTEYE_HOME/ (emptyDir, shared read-write between both containers)
Shared via an emptyDir volume named agenteye-spool.
| Path | Written by | Read by | Purpose |
|---|---|---|---|
$AGENTEYE_HOME/events/*.jsonl | App (AgentEye SDK) | Collector sweeper | Event batches the SDK has flushed, awaiting upload. |
$AGENTEYE_HOME/failed/ | Collector (on upload failure) | You (when debugging) | JSONL files the collector could not upload after retries. |
$AGENTEYE_HOME/config.json | You (optional) | Collector | Optional collector config file (alternative to env vars). |
events/ and failed/ subdirectories are auto-created by the collector on startup; no initContainer needed.
Related docs
- enterprise-docs/collector-installation.md: collector binary options, mTLS config reference, daemon modes.
- enterprise-docs/kubernetes-deployment.md: multi-pod deployment, cert issuance internals, lifecycle and expiry alerts.
- enterprise-docs/api-keys.md: provisioning the collector API key consumed by the pod.
- enterprise-docs/troubleshooting.md: cluster-wide troubleshooting index.

