Deploying a Wazuh Agent DaemonSet
Deploy a real Wazuh agent into Kubernetes using a DaemonSet pattern, write the agent configuration with init containers, connect it to the Wazuh manager services, validate enrollment, and document monitoring coverage.
Domain alignment
What this lesson covers
- Why the DaemonSet model is a good first Kubernetes agent lab
- Why the agent needs a real ossec.conf, not just environment variables
- How the agent reaches the Wazuh manager services inside the cluster
- How to create an agent namespace
- How to copy the Wazuh enrollment password into the agent namespace
- Where to create the DaemonSet manifest file
- How to write agent configuration through init containers
- How to validate scheduling, startup, configuration, and enrollment
- How to verify the agent in the dashboard
- How to document monitoring coverage and blind spots
Step 1: Confirm the Wazuh platform is healthy
Do not deploy agents until the Wazuh manager and indexer are healthy. Agent enrollment depends on the Wazuh manager, and alert flow depends on the manager and indexer working together.
kubectl get pods -n wazuh kubectl get svc -n wazuh kubectl get pvc -n wazuh
You want the indexer, manager master, manager worker, and dashboard pods to be Running and Ready, and the PVCs to be Bound.
Step 2: Identify the manager services the agent will use
kubectl get svc -n wazuh
In this lab, the agent uses two in-cluster Wazuh service paths:
- wazuh.wazuh.svc.cluster.local for registration/enrollment on port 1515.
- wazuh-workers.wazuh.svc.cluster.local for agent event traffic on port 1514.
Step 3: Decide the in-cluster manager addresses
This step defines values that will be written into the agent's ossec.conf file later in Step 6. You are not setting these values globally in Kubernetes, and you are not running a command yet. You are deciding what the DaemonSet manifest will write into the agent configuration.
Agent event manager: wazuh-workers.wazuh.svc.cluster.local Agent enrollment / registration manager: wazuh.wazuh.svc.cluster.local
In the DaemonSet manifest, these appear in the write-ossec-config init container:
- name: WAZUH_MANAGER value: "wazuh-workers.wazuh.svc.cluster.local" - name: WAZUH_REGISTRATION_SERVER value: "wazuh.wazuh.svc.cluster.local"
The init container then writes those values into /var/ossec/etc/ossec.conf before the main Wazuh agent container starts.
Optional DNS test
Before deploying the agent, you can confirm Kubernetes DNS can resolve the Wazuh manager service names.
kubectl create namespace wazuh-agents --dry-run=client -o yaml | kubectl apply -f - kubectl run dns-test -n wazuh-agents --image=busybox:latest --restart=Never -- sleep 3600 kubectl exec -n wazuh-agents dns-test -- nslookup wazuh.wazuh.svc.cluster.local kubectl exec -n wazuh-agents dns-test -- nslookup wazuh-workers.wazuh.svc.cluster.local kubectl delete pod -n wazuh-agents dns-test
Step 4: Create an agent namespace
Keep the agent deployment separate from the Wazuh platform namespace. This makes troubleshooting and cleanup easier.
kubectl create namespace wazuh-agents --dry-run=client -o yaml | kubectl apply -f -
Confirm the namespace exists:
kubectl get namespace wazuh-agents
Step 5: Copy the enrollment password secret
The Wazuh Kubernetes deployment creates a secret named wazuh-authd-pass in the wazuh namespace. The agent DaemonSet runs in wazuh-agents, so it needs access to the same enrollment password in its own namespace.
kubectl get secret -n wazuh wazuh-authd-pass
Use PowerShell to copy the secret value into the agent namespace:
$EncodedAuthdPass = kubectl get secret wazuh-authd-pass -n wazuh -o jsonpath="{.data.authd\.pass}"
$AuthdPass = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($EncodedAuthdPass))
kubectl create secret generic wazuh-authd-pass -n wazuh-agents --from-literal=authd.pass=$AuthdPass --dry-run=client -o yaml | kubectl apply -f -
Confirm the secret exists in the agent namespace:
kubectl get secret -n wazuh-agents wazuh-authd-pass
Step 6: Create the Wazuh agent DaemonSet manifest
Create the manifest inside your Wazuh Kubernetes working directory so the lab files stay together.
cd C:\k8s\wazuh-kubernetes New-Item -ItemType Directory -Force .\lab-manifests notepad .\lab-manifests\wazuh-agent-daemonset.yaml
In Notepad, paste the full manifest below and save the file.
apiVersion: v1
kind: ServiceAccount
metadata:
name: wazuh-agent
namespace: wazuh-agents
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: wazuh-agent
namespace: wazuh-agents
labels:
app: wazuh-agent
spec:
selector:
matchLabels:
app: wazuh-agent
template:
metadata:
labels:
app: wazuh-agent
spec:
serviceAccountName: wazuh-agent
terminationGracePeriodSeconds: 20
tolerations:
- operator: Exists
initContainers:
- name: cleanup-ossec-stale
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-lc"]
args:
- |
set -e
echo "[init] Cleaning stale Wazuh agent pid and lock files..."
mkdir -p /agent/var/run /agent/queue/ossec
rm -f /agent/var/run/*.pid || true
rm -f /agent/queue/ossec/*.lock || true
volumeMounts:
- name: ossec-data
mountPath: /agent
- name: seed-ossec-tree
image: wazuh/wazuh-agent:4.14.5
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-lc"]
args:
- |
set -e
echo "[init] Checking whether /var/ossec needs to be seeded..."
if [ ! -d /agent/bin ]; then
echo "[init] Seeding /var/ossec into the agent hostPath..."
tar -C /var/ossec -cf - . | tar -C /agent -xpf -
else
echo "[init] Existing Wazuh agent data found. Skipping seed."
fi
volumeMounts:
- name: ossec-data
mountPath: /agent
- name: fix-permissions
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-lc"]
args:
- |
set -e
echo "[init] Fixing Wazuh agent file permissions..."
for d in etc logs queue var rids tmp active-response; do
[ -d "/agent/$d" ] && chown -R 999:999 "/agent/$d"
done
chown -R 0:0 /agent/bin /agent/lib || true
find /agent/bin -type f -exec chmod 0755 {} \; || true
volumeMounts:
- name: ossec-data
mountPath: /agent
- name: write-ossec-config
image: busybox:1.36
imagePullPolicy: IfNotPresent
env:
- name: WAZUH_MANAGER
value: "wazuh-workers.wazuh.svc.cluster.local"
- name: WAZUH_PORT
value: "1514"
- name: WAZUH_PROTOCOL
value: "tcp"
- name: WAZUH_REGISTRATION_SERVER
value: "wazuh.wazuh.svc.cluster.local"
- name: WAZUH_REGISTRATION_PORT
value: "1515"
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
command: ["/bin/sh", "-lc"]
args:
- |
set -e
echo "[init] Writing Wazuh agent ossec.conf..."
mkdir -p /agent/etc
cat > /agent/etc/ossec.conf <<'AGENTCONF'
<ossec_config>
<client>
<server>
<address>${WAZUH_MANAGER}</address>
<port>${WAZUH_PORT}</port>
<protocol>${WAZUH_PROTOCOL}</protocol>
</server>
<enrollment>
<enabled>yes</enabled>
<agent_name>${NODE_NAME}</agent_name>
<manager_address>${WAZUH_REGISTRATION_SERVER}</manager_address>
<port>${WAZUH_REGISTRATION_PORT}</port>
<authorization_pass_path>/var/ossec/etc/authd.pass</authorization_pass_path>
</enrollment>
</client>
</ossec_config>
AGENTCONF
sed -i "s|\${WAZUH_MANAGER}|${WAZUH_MANAGER}|g" /agent/etc/ossec.conf
sed -i "s|\${WAZUH_PORT}|${WAZUH_PORT}|g" /agent/etc/ossec.conf
sed -i "s|\${WAZUH_PROTOCOL}|${WAZUH_PROTOCOL}|g" /agent/etc/ossec.conf
sed -i "s|\${NODE_NAME}|${NODE_NAME}|g" /agent/etc/ossec.conf
sed -i "s|\${WAZUH_REGISTRATION_SERVER}|${WAZUH_REGISTRATION_SERVER}|g" /agent/etc/ossec.conf
sed -i "s|\${WAZUH_REGISTRATION_PORT}|${WAZUH_REGISTRATION_PORT}|g" /agent/etc/ossec.conf
chown 999:999 /agent/etc/ossec.conf
chmod 0640 /agent/etc/ossec.conf
echo "[init] ossec.conf created:"
cat /agent/etc/ossec.conf
volumeMounts:
- name: ossec-data
mountPath: /agent
- name: copy-authd-pass
image: busybox:1.36
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-lc"]
args:
- |
set -e
echo "[init] Copying authd.pass from Kubernetes Secret..."
mkdir -p /agent/etc
cp /secret/authd.pass /agent/etc/authd.pass
chown 0:999 /agent/etc/authd.pass
chmod 0640 /agent/etc/authd.pass
ls -l /agent/etc/authd.pass
volumeMounts:
- name: ossec-data
mountPath: /agent
- name: wazuh-authd-pass
mountPath: /secret/authd.pass
subPath: authd.pass
readOnly: true
containers:
- name: wazuh-agent
image: wazuh/wazuh-agent:4.14.5
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-lc"]
args:
- |
set -e
ln -sf /var/ossec/etc/ossec.conf /etc/ossec.conf || true
exec /init
env:
- name: WAZUH_MANAGER
value: "wazuh-workers.wazuh.svc.cluster.local"
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
securityContext:
runAsUser: 0
allowPrivilegeEscalation: true
capabilities:
add:
- SETGID
- SETUID
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: ossec-data
mountPath: /var/ossec
volumes:
- name: varlog
hostPath:
path: /var/log
type: Directory
- name: ossec-data
hostPath:
path: /var/lib/wazuh-agent
type: DirectoryOrCreate
- name: wazuh-authd-pass
secret:
secretName: wazuh-authd-pass
Confirm the file exists
Get-Item .\lab-manifests\wazuh-agent-daemonset.yaml
Validate the manifest before applying it
kubectl apply --dry-run=client -f .\lab-manifests\wazuh-agent-daemonset.yaml
Step 7: Apply the DaemonSet
Apply the manifest from the same directory where you created it.
kubectl apply -f .\lab-manifests\wazuh-agent-daemonset.yaml
Step 8: Verify the DaemonSet exists
kubectl get daemonset -n wazuh-agents kubectl get pods -n wazuh-agents -o wide
In a single-node lab, expect one Wazuh agent pod. In a multi-node lab, expect one agent pod per schedulable node.
Step 9: Watch the agent logs
First capture the agent pod name into a PowerShell variable:
$AgentPod = kubectl get pods -n wazuh-agents -l app=wazuh-agent -o jsonpath="{.items[0].metadata.name}"
Check the init container that writes ossec.conf:
kubectl logs -n wazuh-agents $AgentPod -c write-ossec-config
You should see the generated manager values:
<address>wazuh-workers.wazuh.svc.cluster.local</address> <manager_address>wazuh.wazuh.svc.cluster.local</manager_address>
Then check the main agent logs:
kubectl logs -n wazuh-agents -l app=wazuh-agent --tail=100
The agent should no longer fail with an empty manager address.
Step 10: Troubleshoot agent startup
Agent says Invalid server address found: ''
This means the agent started without a valid manager address in ossec.conf.
wazuh-agentd: ERROR: (4112): Invalid server address found: '' wazuh-agentd: ERROR: (1215): No client configured. Exiting.
Fix this by confirming the write-ossec-config init container exists and wrote the manager address into the config file.
$AgentPod = kubectl get pods -n wazuh-agents -l app=wazuh-agent -o jsonpath="{.items[0].metadata.name}"
kubectl logs -n wazuh-agents $AgentPod -c write-ossec-config
Image pull failure
kubectl describe pod -n wazuh-agents <agent-pod-name>
Confirm the image tag exists and the node can pull from the registry.
Secret error
kubectl get secret -n wazuh-agents wazuh-authd-pass kubectl describe pod -n wazuh-agents <agent-pod-name>
If the secret or key name is wrong, the pod may fail during the copy-authd-pass init container.
Manager connection or DNS error
kubectl run dns-test -n wazuh-agents --image=busybox:latest --restart=Never -- sleep 3600 kubectl exec -n wazuh-agents dns-test -- nslookup wazuh.wazuh.svc.cluster.local kubectl exec -n wazuh-agents dns-test -- nslookup wazuh-workers.wazuh.svc.cluster.local kubectl delete pod -n wazuh-agents dns-test
If DNS fails, troubleshoot cluster DNS or service naming before troubleshooting Wazuh enrollment.
Step 11: Verify the agent in the Wazuh dashboard
Open the Wazuh dashboard and navigate to agent management or the agent summary view. Confirm the Kubernetes node agent appears.
Expected evidence: - Agent visible in Wazuh - Agent name matches the Kubernetes node name or configured agent name - Agent status is active - Agent group is kubernetes, if configured - Recent keepalive or event timestamp is present
Step 12: Generate a safe node-level test event
Generate a simple log event on the node through a lab pod. Keep this benign and documented.
kubectl run soc-agent-test -n wazuh-agents --image=busybox:latest --restart=Never -- sh -c "echo Wazuh agent DaemonSet lab test && sleep 30" kubectl logs -n wazuh-agents soc-agent-test kubectl delete pod -n wazuh-agents soc-agent-test
Then search the Wazuh dashboard around the timestamp of the test. Depending on agent configuration and collected log paths, this may show directly as an event or may support timing correlation.
Step 13: Document monitoring coverage
Agent deployment model: Namespace: DaemonSet name: Agent image/tag: Manager event service address: Manager registration service address: Nodes covered: Nodes not covered: Host paths mounted: Secrets used: Agent visible in dashboard: Known blind spots: Next tuning action:
Cleanup note
If you need to remove the lab agent deployment, delete the DaemonSet and then decide whether to keep or remove the namespace and secret.
kubectl delete daemonset wazuh-agent -n wazuh-agents kubectl delete serviceaccount wazuh-agent -n wazuh-agents
To fully remove the agent lab namespace:
kubectl delete namespace wazuh-agents
Check your understanding
- Why is a DaemonSet useful for Kubernetes agent deployment?
- Why should agents be deployed after Wazuh manager/indexer health is confirmed?
- Why use a separate namespace for agents?
- Why does the agent need ossec.conf to contain a valid manager address?
- Where is WAZUH_MANAGER set in this lab?
- Why is the registration service different from the event service?
- Why use a Kubernetes Secret for the enrollment password?
- What does one agent pod per node tell you?
- Why is EXTERNAL-IP <pending> not a blocker for in-cluster agent communication?
- What should you check if the agent cannot resolve the manager service name?
- Why document hostPath mounts?
- What proves the agent is enrolled?
- Why should monitoring coverage include blind spots?
Answer key
- It schedules one agent on each node, which is useful for node-level monitoring.
- Agents must connect to a working manager and the platform must be able to process data.
- It separates monitoring agents from the Wazuh platform and simplifies cleanup/troubleshooting.
- Without a valid <client> server address, the Wazuh agent has no manager to connect to and exits.
- It is set in the DaemonSet manifest, specifically in the write-ossec-config init container, which writes the value into ossec.conf.
- The master service handles enrollment/registration on port 1515, while the worker service receives agent event traffic on port 1514.
- It avoids putting the enrollment password directly in the manifest.
- The DaemonSet is scheduling as expected across available nodes.
- Pods can communicate through ClusterIP/DNS service names inside the cluster.
- Cluster DNS, service name, namespace, and service existence.
- Host mounts affect security boundaries and visibility.
- Agent visible and active in the Wazuh dashboard with recent communication.
- Because no alert may mean no collection, not no issue.