Docs/Agent management
Docs/Agent management

Kubernetes deployment

Run the agent in your own cluster, using the published Helm chart or plain YAML manifests.

Both forms live in the public vaurd-agent-k8s repository and produce the same architecture. Use the Helm chart unless your review process needs literal YAML.

For what you are deploying and why, see Deploying the agent.

Before you start

  • Kubernetes 1.25 or newer, and Helm 3.8+ if you use the chart.
  • A storage class that supports `ReadWriteMany`. All three components share one database file. On a single-node cluster you can use ReadWriteOnce instead, see Single-node clusters.
  • Your `config.yml` and authentication key, from steps 2 and 3 of registration.
  • Network access as described in Network access.

Deploying with Helm

Step 1: Add the repository

BASH
helm repo add vaurd https://vaurd.github.io/vaurd-agent-k8s
helm repo update
helm search repo vaurd/vaurd-agent --versions

Step 2: Write a values file

The chart does not take the config.yml file directly. It builds one for you from the values you set, so you translate the settings from the downloaded file into values.yaml. The mapping is in Translating your configuration below.

my-values.yamlYAML
config:
  license: <your-authentication-key>
  platform:
    url: am.vaurd.com:5000
    caCert: |
      -----BEGIN CERTIFICATE-----
      <vaurd agent manager CA certificate>
      -----END CERTIFICATE-----
  tls:
    serverCert: |
      -----BEGIN CERTIFICATE-----
      <server_cert from your config.yml>
      -----END CERTIFICATE-----
    serverKey: |
      -----BEGIN RSA PRIVATE KEY-----
      <server_key from your config.yml>
      -----END RSA PRIVATE KEY-----
    caCert: |
      -----BEGIN CERTIFICATE-----
      <ca_cert from your config.yml>
      -----END CERTIFICATE-----
  sources:
    - name: orders_db
      type: database_postgres
      params:
        host: postgres.databases.svc.cluster.local
        port: 5432
        user: vaurd
        password: <password>
        database: orders

persistence:
  storageClass: efs-sc
  size: 20Gi

ingestion:
  replicaCount: 1
worker:
  replicaCount: 1

The three values under config.tls are the certificates the platform already put in your downloaded config.yml. Copy them across verbatim, PEM block and all: server_cert becomes serverCert, server_key becomes serverKey, and ca_cert becomes caCert. They encrypt gRPC between the agent's own components.

Two different certificates are both called caCert

config.platform.caCert is the CA for the Vaurd platform, and it comes from the vaurd.ca_cert block in your config file. config.tls.caCert is the agent's own internal CA, and it comes from the top-level ca_cert. They are not interchangeable, and the two blocks sit close together in both files, so it is an easy one to get wrong.

Set all three config.tls values or none of them. Supplying only caCert makes the components attempt TLS against servers that are serving plaintext, and every one of them fails to start. Leave config.tls.serverName at its default of vaurd-agent; it has to match the certificate's subject alternative name, not any Kubernetes Service name.

Keep secrets out of version control. The license key, the private key and any source passwords all end up in a Kubernetes Secret, so feed them in from your own secret store rather than committing them.

Step 3: Install

BASH
helm install vaurd vaurd/vaurd-agent --version 0.1.1 \
  --namespace vaurd --create-namespace \
  -f my-values.yaml

Always pass --version. An unpinned install picks up whatever is newest at the time, which makes your deployments impossible to reproduce.

You do not need to choose a NATS password. The chart generates one for the bundled server on install, stores it in a Secret, and reuses it on every upgrade. You only set nats.auth.password yourself if you point the agent at an external NATS server, or if you render the chart offline with helm template or a GitOps tool.

Step 4: Verify

BASH
kubectl -n vaurd get pods -l app.kubernetes.io/instance=vaurd
kubectl -n vaurd logs deploy/vaurd-vaurd-agent-core

Core answers the standard gRPC health check. Forward its port and ask it directly with grpcurl, reading the CA certificate out of the config.yml you downloaded during registration:

BASH
kubectl -n vaurd port-forward svc/vaurd-vaurd-agent-core 5001:5001
grpcurl -cacert <(yq -r '.ca_cert' config.yml) localhost:5001 grpc.health.v1.Health/Check
JSON
{
  "status": "SERVING"
}

Run the same check against your ingress or external endpoint as well, since that is the address Vaurd will call during registration.

It is normal for ingestion and worker pods to restart once or twice on a first install while they wait for core. Once core is ready, they settle.

The agent is running but not registered yet. Go back to step 5 of registration and start registration from the web application.

Translating your configuration

The platform gives you a config.yml. The chart generates its own from values.yaml. This table maps one to the other.

In `config.yml`In `values.yaml`
licenseconfig.license
vaurd.manager_urlconfig.platform.url
vaurd.ca_certconfig.platform.caCert
vaurd.ping_interval_secondsconfig.platform.pingIntervalSeconds
vaurd.status_guard_poll_secondsconfig.platform.statusGuardPollSeconds
sourcesconfig.sources
pluginsconfig.plugins
telemetry.batch_sizeconfig.telemetry.batchSize
telemetry.batch_wait_msconfig.telemetry.batchWaitMs
telemetry.stream_ttl_secondsconfig.telemetry.streamTtlSeconds
telemetry.max_bytesconfig.telemetry.maxBytes
evaluator.max_concurrencyworker.maxConcurrency
evaluator.max_delivery_attemptsworker.maxDeliveryAttempts
server_certconfig.tls.serverCert
server_keyconfig.tls.serverKey
ca_certconfig.tls.caCert
services.core.server_nameconfig.tls.serverName
services.nats.*nats.auth.*, or nats.externalEndpoint
portcore.service.port

Two settings are missing from that table because you never set them yourself. The chart writes services.storage.data_dir as /data, the path the shared volume is mounted at, and it derives services.core.address from the core Service name and core.service.port. Both depend on in-cluster addressing rather than on anything you chose.

config.plugins, config.telemetry and worker.maxDeliveryAttempts are written into the generated file only when you set them. Leave them out and the keys are absent entirely, so the agent applies its own defaults. You never have to restate a value just to keep it.

Using the downloaded file as-is

If you would rather not translate anything, create a Secret from your config.yml and point the chart at it:

BASH
kubectl -n vaurd create secret generic my-agent-config --from-file=config.yml
my-values.yamlYAML
config:
  existingSecret: my-agent-config

Everything else under config is then ignored. Before you do this, edit the file so its in-cluster addresses are right, exactly as described for Docker but with Kubernetes names:

config.ymlYAML
services:
  storage:
    data_dir: /data
  nats:
    endpoint: nats://vaurd-nats:4222
  core:
    address: vaurd-agent-core:5001
    server_name: vaurd-agent

Deploying with the raw manifests

The manifests are numbered in apply order. Clone the repository, since you need to edit them:

BASH
git clone https://github.com/Vaurd/vaurd-agent-k8s
cd vaurd-agent-k8s

Step 1. Open k8s/01-config.yaml and replace every <placeholder>: your license token, the platform address and CA certificate, and a NATS password. The password appears in two Secrets and both must match.

The server_cert, server_key and ca_cert keys are commented out in that file. Your downloaded config.yml has all three filled in already, so uncomment them and paste the PEM blocks across unchanged. Uncomment all three together or leave all three commented out: supplying only ca_cert makes every component fail to start.

Step 2. In k8s/03-storage.yaml, uncomment storageClassName and set a class that supports ReadWriteMany.

Step 3. Set your image tags. The manifests reference vaurd/vaurd-agent-*:v0.1.0. If your registry is private, uncomment imagePullSecrets in each workload.

Step 4. Apply in order:

BASH
kubectl apply -f k8s/00-namespace.yaml
kubectl apply -f k8s/01-config.yaml
kubectl apply -f k8s/02-nats.yaml
kubectl apply -f k8s/03-storage.yaml
kubectl apply -f k8s/04-core.yaml
kubectl apply -f k8s/05-ingestion.yaml
kubectl apply -f k8s/06-worker.yaml

Applying the whole directory at once also works. Kubernetes reconciles the dependencies itself, though pods restart a few times while they wait.

Step 5. Verify:

BASH
kubectl -n vaurd get pods
kubectl -n vaurd logs deploy/vaurd-agent-core
kubectl -n vaurd port-forward svc/vaurd-agent-core 5001:5001
grpcurl -cacert <(yq -r '.ca_cert' config.yml) localhost:5001 grpc.health.v1.Health/Check

The health check returns SERVING once core is up.

Exposing the ingestion API

Producers inside the cluster need no ingress. They can post directly to the Service:

http://vaurd-agent-ingestion.vaurd.svc.cluster.local:5002/api/v1/ingest

To reach it from outside, both forms use ingress-nginx, which behaves the same on every cluster.

With Helm:

my-values.yamlYAML
ingress:
  enabled: true
  className: nginx
  host: agent.example.com
  tls:
    enabled: true
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod

With the manifests, edit the host in k8s/07-ingress.yaml and apply it. TLS is commented out by default so the file applies cleanly without a certificate on hand; uncomment the tls: block and the cert-manager annotation for production.

Storage

The three components share one SQLite database file, so the volume has to be mounted read-write by pods on different nodes. These storage classes are known to work:

PlatformStorage class
AWSEFS (efs.csi.aws.com)
AzureAzure Files (file.csi.azure.com)
GCPFilestore (filestore.csi.storage.gke.io)
Self-hostedCephFS, or NFS with working POSIX locking
Check file locking before production

SQLite relies on POSIX advisory locks to keep concurrent readers and writers consistent. The options above implement them correctly. Some plain NFS servers do not, and a broken lock implementation can corrupt the database. If you are unsure, ask your storage provider whether fcntl byte-range locking is supported.

Single-node clusters

On minikube, k3s or kind there is no ReadWriteMany storage class. Use ReadWriteOnce instead and pin every component to the one node:

minikube-values.yamlYAML
persistence:
  accessMode: ReadWriteOnce

core:
  nodeSelector: { kubernetes.io/hostname: minikube }
ingestion:
  nodeSelector: { kubernetes.io/hostname: minikube }
worker:
  nodeSelector: { kubernetes.io/hostname: minikube }

ingress:
  enabled: true
  className: nginx
  tls:
    enabled: false

A nip.io hostname pointing at the cluster IP needs no DNS setup:

BASH
minikube addons enable ingress
helm install vaurd vaurd/vaurd-agent --version 0.1.1 \
  -n vaurd --create-namespace -f minikube-values.yaml \
  --set config.license='<token>' \
  --set config.platform.url='am.vaurd.com:5000' \
  --set ingress.host="vaurd.$(minikube ip).nip.io"

With the raw manifests, make the same edits by hand: accessModes: ReadWriteOnce in 03-storage.yaml, and a nodeSelector in each of 04-core.yaml, 05-ingestion.yaml and 06-worker.yaml.

Scaling

Ingestion and worker pods consume from NATS work queues, where each message goes to exactly one consumer, so they scale freely:

BASH
helm upgrade vaurd vaurd/vaurd-agent --version 0.1.1 \
  -n vaurd -f my-values.yaml --set worker.replicaCount=8

Core must stay at one replica. It runs singleton background tasks, the heartbeat to the platform and the shipping of actions and telemetry, and it owns parts of the shared database. The chart does not expose a replica count for it. Its rollout strategy is Recreate, so expect a brief gap in heartbeat and telemetry during an upgrade.

Updating configuration

The agent reads settings only from its mounted config.yml, so every change goes through the chart or the Secret.

With Helm, change your values file and run helm upgrade. A config change rolls the pods automatically.

With the manifests, edit the Secret and restart by hand:

BASH
kubectl -n vaurd rollout restart deploy/vaurd-agent-core \
  deploy/vaurd-agent-ingestion deploy/vaurd-agent-worker

Upgrading and rolling back

BASH
helm upgrade vaurd vaurd/vaurd-agent --version 0.1.1 -n vaurd -f my-values.yaml
helm history vaurd -n vaurd
helm rollback vaurd 1 -n vaurd

Uninstalling

BASH
helm uninstall vaurd -n vaurd
kubectl -n vaurd delete pvc -l app.kubernetes.io/instance=vaurd

Helm does not delete PersistentVolumeClaims. The second command removes the agent's data, which cannot be undone.

Troubleshooting

Pods stuck in `Pending`. The shared volume could not be provisioned. Run kubectl -n vaurd describe pvc. Usually the storage class is missing or does not support ReadWriteMany.

Ingestion or worker pods restarting. They cannot reach core. Confirm core is ready, then check its logs for a registration failure. A bad license token or an unreachable platform URL shows up there.

Core never becomes ready. It cannot reach the Vaurd platform. Verify the platform URL and CA certificate, and that egress from the cluster is allowed. Port-forward core and run grpcurl -cacert <(yq -r '.ca_cert' config.yml) localhost:5001 grpc.health.v1.Health/Check to confirm whether the process itself is up.

Configuration changes have no effect. The agent reads settings only from its mounted config.yml. Environment variables are ignored, so the change has to be in the Secret, followed by a rollout restart.