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
ReadWriteOnceinstead, 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
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.
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: 1The 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.
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
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
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:
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
{
"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.
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:
kubectl -n vaurd create secret generic my-agent-config --from-file=config.yml
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:
services:
storage:
data_dir: /data
nats:
endpoint: nats://vaurd-nats:4222
core:
address: vaurd-agent-core:5001
server_name: vaurd-agentDeploying with the raw manifests
The manifests are numbered in apply order. Clone the repository, since you need to edit them:
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:
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:
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:
ingress:
enabled: true
className: nginx
host: agent.example.com
tls:
enabled: true
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prodWith 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:
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:
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: falseA nip.io hostname pointing at the cluster IP needs no DNS setup:
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:
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:
kubectl -n vaurd rollout restart deploy/vaurd-agent-core \ deploy/vaurd-agent-ingestion deploy/vaurd-agent-worker
Upgrading and rolling back
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
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.