A complete technical guide to deploying Fluentd on Windows Server 2022 nodes in VKS — covering the full pipeline from log collection and Kubernetes metadata enrichment through to Elasticsearch, VMware Aria Operations for Logs, and local file output.
Author: Julius Nicolescu Date: March 2026
- Overview
- Architecture
- Repository Structure
- Prerequisites
- Connecting to the VKS Cluster
- Fluentd Deployment
- Fluentd Configuration and Plugins
- fluent.conf — Main Entry Point
- Plugin: tail — Log Collection
- Plugin: kubernetes_metadata — Metadata Enrichment
- Plugin: record_transformer — Field Flattening
- Plugin: file — Local File Output
- Plugin: elasticsearch — Elasticsearch Output
- Plugin: http — VCF Operations for Logs Output
- Plugin: vmware_loginsight — Native Aria Output (Deprecated)
- Counter Test Application
- Demo: Elasticsearch and Kibana
- Verification and Troubleshooting
- References
Running Windows workloads on Kubernetes means logs written to stdout/stderr by containers are captured by the Windows kubelet and stored as plain-text files on the node under C:\var\log\containers\. Without a log collection agent, those files are only accessible by SSHing to the node or running kubectl logs one pod at a time — neither of which scales.
This project deploys Fluentd as a Kubernetes DaemonSet (one pod per Windows node) to:
- Continuously tail all container log files on the node
- Parse the kubelet log format into structured fields
- Enrich each log record with Kubernetes metadata (pod, namespace, container, node)
- Forward enriched records to a configurable output backend
Four output backends are provided, each as a standalone Fluentd configuration file. Only one is active at a time and is selected by editing a single @include line.
A single-node Elasticsearch + Kibana stack and a log-generating test pod are included for end-to-end validation with no external dependencies.
Windows Node (Windows Server 2022)
┌───────────────────────────────────────────────────────────────────┐
│ │
│ Kubelet writes container logs to: │
│ C:\var\log\containers\<pod>_<ns>_<container>-<hash>.log │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ Fluentd Pod │ ← DaemonSet (1 per node) │
│ │ │ │
│ │ [1] tail source │ read log files │
│ │ [2] regexp parse │ extract time/stream/message │
│ │ [3] k8s_metadata │ inject pod/namespace/labels │
│ │ [4] record_xform │ flatten + rename fields │
│ └─────────┬──────────┘ │
│ │ │
│ Position file: C:\var\log\fluentd\fluentd-containers.pos │
│ (tracks read offset — survives pod restarts) │
└─────────────────────────┼─────────────────────────────────────────┘
│
┌───────────────▼──────────────────────────────┐
│ Output Backend (one active at a time) │
├──────────────────────────────────────────────┤
│ file-fluentd.conf local file │
│ elastic-fluentd.conf Elasticsearch │
│ operations-for-logs-fluentd.conf Aria HTTP │
│ loginsight-fluentd.conf Aria native (⚠ deprecated) │
└──────────────────────────────────────────────┘
fluentd-namespace.yaml Namespace "fluentd" with privileged pod-security labels
fluentd-rbac.yaml ServiceAccount, ClusterRole, ClusterRoleBinding
fluentd-configmap-windows.yaml Complete Fluentd pipeline: all conf files in one ConfigMap
fluentd-daemonset-windows.yaml DaemonSet — runs on Windows nodes only
counter-windows.yaml Test pod — emits a numbered log line every 5 seconds
demo-elastic-kibana.yaml Namespace "elastic-kibana" for the demo ELK stack
demo-elastic-7.17.29.yaml Single-node Elasticsearch 7.17.29 + ClusterIP + LoadBalancer
demo-kibana-7.17.29.yaml Kibana 7.17.29 + ClusterIP + LoadBalancer
kubectlconfigured to reach the VKS clustervcfCLI installed for kubeconfig context management- At least one Windows Server 2022 worker node in the cluster
- Node taint
os=windows:NoScheduleapplied to Windows nodes
Use the vcf CLI to create or refresh a kubeconfig context for the target cluster.
export SUPERVISOR_IP=192.168.231.7
export VCF_CLI_VSPHERE_PASSWORD='<password>'
export CLUSTER_NAMESPACE=lab-namespace-01
export CLUSTER_NAME=dev-cluster-05
# Delete and recreate a context (use this when the context has expired)
vcf context delete ${CLUSTER_NAME} -y
vcf context create ${CLUSTER_NAME} \
--endpoint ${SUPERVISOR_IP} \
--username administrator@vsphere.local \
--workload-cluster-namespace ${CLUSTER_NAMESPACE} \
--workload-cluster-name ${CLUSTER_NAME} \
--insecure-skip-tls-verify
# List available contexts and switch to the target cluster
vcf context list
vcf context use ${CLUSTER_NAME}:${CLUSTER_NAME}
# Or simply refresh an existing context without recreating it
vcf context refresh ${CLUSTER_NAME} --insecure-skip-tls-verifyVerify cluster nodes:
kubectl get nodes -l kubernetes.io/os=linux
kubectl get nodes -l kubernetes.io/os=windowsExample output from dev-cluster-05:
NAME STATUS ROLES AGE VERSION
dev-cluster-05-tl497-vg2dg Ready control-plane 22d v1.34.1+vmware.1
NAME STATUS ROLES AGE VERSION
dev-cluster-05-win2022-ls7zk-vg5bz-fl6p6 Ready <none> 32h v1.34.1+vmware.1
Apply the manifests in order. The namespace and RBAC must exist before the ConfigMap and DaemonSet are created.
kubectl apply -f fluentd-namespace.yaml
kubectl apply -f fluentd-rbac.yaml
kubectl apply -f fluentd-configmap-windows.yaml
kubectl apply -f fluentd-daemonset-windows.yamlMonitor the rollout:
kubectl -n fluentd rollout status ds/fluentd
kubectl -n fluentd get pods
kubectl -n fluentd logs -l k8s-app=fluentd-logging --tail=50Switching output plugins requires editing fluent.conf in the ConfigMap, then restarting the pods. ConfigMap changes are not picked up automatically.
# After editing fluentd-configmap-windows.yaml:
kubectl apply -f fluentd-configmap-windows.yaml
kubectl -n fluentd rollout restart ds/fluentd
kubectl -n fluentd rollout status ds/fluentdRemove Fluentd:
kubectl delete -f fluentd-daemonset-windows.yaml
kubectl delete -f fluentd-configmap-windows.yaml
kubectl delete -f fluentd-rbac.yaml
kubectl delete -f fluentd-namespace.yamlAll configuration is stored in fluentd-configmap-windows.yaml. The ConfigMap is mounted into each Fluentd pod as a directory at C:\fluent\conf, where each ConfigMap key becomes a .conf file.
C:\fluent\conf\
fluent.conf ← main entry point
pods-fluentd.conf ← shared collection + enrichment pipeline
file-fluentd.conf ← output: local file
elastic-fluentd.conf ← output: Elasticsearch
operations-for-logs-fluentd.conf ← output: VCF Ops for Logs (HTTP)
loginsight-fluentd.conf ← output: Aria native plugin (deprecated)
@include pods-fluentd.conf
# --- uncomment exactly one output plugin ---
# @include file-fluentd.conf
# @include elastic-fluentd.conf
# @include operations-for-logs-fluentd.conf
@include loginsight-fluentd.conf
pods-fluentd.conf is always included. It defines the log source and the two enrichment filters that run regardless of which output backend is active. Only one @include for an output plugin should be uncommented at any time — having two outputs active simultaneously will cause duplicate log delivery.
Type: built-in input plugin
File: pods-fluentd.conf
Docs: https://docs.fluentd.org/input/tail
The tail plugin continuously reads new lines appended to matching files, similar to tail -F. It is the entry point for all log data in this pipeline.
<source>
@type tail
path /var/log/containers/*.log
pos_file /var/log/fluentd/fluentd-containers.pos
exclude_path ["/var/log/containers/fluent*"]
path_key log_path
read_from_head true
tag kubernetes.*
<parse> ... </parse>
</source>
Key settings:
| Setting | Value | Purpose |
|---|---|---|
path |
/var/log/containers/*.log |
Match all container log files on the node. Forward-slash paths work inside Windows containers because Ruby normalises path separators. |
pos_file |
/var/log/fluentd/fluentd-containers.pos |
Stores the byte offset last read for each file. Mounted on a hostPath volume so the position survives pod restarts — prevents re-reading logs already delivered. |
exclude_path |
["/var/log/containers/fluent*"] |
Skips Fluentd's own log file to avoid a feedback loop where Fluentd logs become input to itself. |
path_key |
log_path |
Adds the source file path as a record field. The file name encodes pod name, namespace, and container name, which the metadata filter uses. |
read_from_head |
true |
Reads each file from the beginning on first encounter (e.g. when a new pod starts). Without this, only lines written after Fluentd starts are read. |
tag |
kubernetes.* |
Tags all events with a prefix that downstream filters and outputs match against. |
Log line format (kubelet container log format):
2024-01-15T10:23:45.123456789Z stdout F Hello World from counter-windows
│ │ │ │
│ │ │ └─ log message
│ │ └─── logtag (F = full line, P = partial)
│ └────────── stream (stdout or stderr)
└──────────────────────────────────────── RFC3339Nano timestamp
The regexp parser extracts these four fields:
expression: /^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<log>.*)$/
| Field | Description |
|---|---|
time |
Event timestamp (parsed as %Y-%m-%dT%H:%M:%S.%N%Z) |
stream |
stdout or stderr |
logtag |
F (full line) or P (partial — CRI log rotation artifact) |
log |
Raw log message from the container |
Type: filter plugin
Gem: fluent-plugin-kubernetes_metadata_filter
File: pods-fluentd.conf
Docs: https://github.com/fabric8io/fluent-plugin-kubernetes_metadata_filter
<filter **>
@type kubernetes_metadata
@id filter_kube_metadata
</filter>
This filter looks up the Kubernetes API to find the pod that produced each log record and injects a nested kubernetes hash into every event. The pod is identified from the log_path field set by the tail source — the container log filename follows the pattern <pod>_<namespace>_<container>-<hash>.log.
Fields injected into each record:
{
"kubernetes": {
"namespace_name": "default",
"pod_name": "counter-windows",
"container_name": "count",
"host": "dev-cluster-05-win2022-ls7zk-vg5bz-fl6p6",
"labels": {
"app": "counter-windows"
},
"annotations": { ... }
}
}Why ClusterRole is required: the plugin queries the Kubernetes API for pod and namespace objects across all namespaces. A namespaced Role would only allow lookups within the fluentd namespace, missing pods in every other namespace. The ClusterRoleBinding in fluentd-rbac.yaml grants the fluentd ServiceAccount the necessary cluster-wide read permissions.
The plugin caches pod metadata in memory to avoid an API call per log line. Cached entries are refreshed when a pod changes or the cache TTL expires.
Type: built-in filter plugin
File: pods-fluentd.conf
Docs: https://docs.fluentd.org/filter/record_transformer
<filter **>
@type record_transformer
enable_ruby true
<record>
namespace ${record.dig("kubernetes", "namespace_name")}
pod ${record.dig("kubernetes", "pod_name")}
container ${record.dig("kubernetes", "container_name")}
node ${record.dig("kubernetes", "host")}
log_type kubernetes
k8s_cluster dev-cluster-05
message ${record["log"]}
</record>
remove_keys log,kubernetes,stream,logtag,log_path
</filter>
This filter runs after the metadata filter and does two things:
-
Flatten the nested
kuberneteshash into individual top-level fields. This produces a flat record that is easier to index and query in Elasticsearch or Operations for Logs without needing dot-notation field names. -
Remove intermediate fields that were needed for processing but are not useful in the final record (
log,kubernetes,stream,logtag,log_path).
enable_ruby true is required to use record.dig(), which safely traverses the nested hash without raising an error if a key is absent.
Record shape after transformation:
{
"@timestamp": "2024-01-15T10:23:45.123456789Z",
"namespace": "default",
"pod": "counter-windows",
"container": "count",
"node": "dev-cluster-05-win2022-ls7zk-vg5bz-fl6p6",
"log_type": "kubernetes",
"k8s_cluster": "dev-cluster-05",
"message": "42: WINDOWS TESTMSG 2024-01-15T10:23:45.1234567+00:00"
}Type: built-in output plugin
File: file-fluentd.conf
Docs: https://docs.fluentd.org/output/file
<match **>
@type file
path /var/log/fluentd/file-test-log
append true
<format>
@type json
</format>
<buffer>
flush_interval 10s
</buffer>
</match>
Writes each log record as a JSON object to a date-suffixed file on the node:
/var/log/fluentd/file-test-log.20240115.log (inside container)
C:\var\log\fluentd\file-test-log.20240115.log (on the Windows host)
Use this plugin first when setting up or debugging the pipeline. It requires no external system — if records appear in the file, the tail source, metadata filter, and record transformer are all working correctly.
To activate:
# fluent.conf
@include pods-fluentd.conf
@include file-fluentd.conf
To verify from inside the pod:
# List generated output files
Get-ChildItem C:\var\log\fluentd\
# Read and pretty-print the latest output file
Get-Content C:\var\log\fluentd\file-test-log.20240115.log |
ForEach-Object { $_ | ConvertFrom-Json }Type: output plugin
Gem: fluent-plugin-elasticsearch
File: elastic-fluentd.conf
Docs: https://github.com/uken/fluent-plugin-elasticsearch
<match **>
@type elasticsearch
@id out_es
@log_level info
host "#{ENV['FLUENT_ELASTICSEARCH_HOST']}"
port "#{ENV['FLUENT_ELASTICSEARCH_PORT']}"
user "#{ENV['FLUENT_ELASTICSEARCH_USER']}"
password "#{ENV['FLUENT_ELASTICSEARCH_PASSWORD']}"
scheme http
logstash_format true
logstash_prefix fluentd-windows
type_name fluentd
include_tag_key true
<buffer> ... </buffer>
</match>
Sends log records to Elasticsearch using the Logstash-compatible index naming convention. With logstash_format true and logstash_prefix fluentd-windows, Fluentd creates a new index each day named fluentd-windows-YYYY.MM.DD. This matches the pattern expected by Kibana's default index management and allows time-based data management (e.g. deleting old indices by date).
Connection settings are read from environment variables set in the DaemonSet, so the Elasticsearch address can be changed without modifying the ConfigMap. All buffer parameters also accept environment variable overrides via FLUENT_ELASTICSEARCH_BUFFER_* variables.
| Setting | Environment variable / Default |
|---|---|
| Host | FLUENT_ELASTICSEARCH_HOST — 192.168.231.11 |
| Port | FLUENT_ELASTICSEARCH_PORT — 9200 |
| Username | FLUENT_ELASTICSEARCH_USER — elastic |
| Password | FLUENT_ELASTICSEARCH_PASSWORD |
| Index prefix | fluentd-windows |
| Index name format | fluentd-windows-YYYY.MM.DD |
| Scheme | http |
| Flush threads | FLUENT_ELASTICSEARCH_BUFFER_FLUSH_THREAD_COUNT — 8 |
| Flush interval | FLUENT_ELASTICSEARCH_BUFFER_FLUSH_INTERVAL — 5s |
| Chunk size | FLUENT_ELASTICSEARCH_BUFFER_CHUNK_LIMIT_SIZE — 2M |
| Queue length | FLUENT_ELASTICSEARCH_BUFFER_QUEUE_LIMIT_LENGTH — 32 |
| Max retry interval | FLUENT_ELASTICSEARCH_BUFFER_RETRY_MAX_INTERVAL — 30 |
| Retry | forever |
retry_forever true means Fluentd will keep retrying failed deliveries indefinitely rather than dropping records. This is appropriate for a log pipeline where losing data is undesirable and temporary Elasticsearch unavailability is expected.
To activate:
# fluent.conf
@include pods-fluentd.conf
@include elastic-fluentd.conf
To verify from outside the cluster:
# Cluster health
curl -u elastic:elastic http://<ES-EXTERNAL-IP>:9200/_cluster/health | jq
# List fluentd indices
curl -u elastic:elastic \
"http://<ES-EXTERNAL-IP>:9200/_cat/indices/fluentd-windows-*?v&s=index"
# Fetch the 5 most recent log records
curl -u elastic:elastic \
"http://<ES-EXTERNAL-IP>:9200/fluentd-windows-*/_search?size=5&sort=@timestamp:desc&pretty"Type: built-in output plugin
File: operations-for-logs-fluentd.conf
Docs: https://docs.fluentd.org/output/http
<match **>
@type http
endpoint http://192.168.200.40:9000/api/v2/events
http_method post
content_type application/json
tls_verify_mode none
json_array true
<format>
@type json
</format>
<inject>
time_key timestamp
time_type string
time_format %Y-%m-%dT%H:%M:%SZ
</inject>
<buffer>
flush_interval 10s
chunk_limit_size 256k
</buffer>
</match>
Sends log records to VMware Aria Operations for Logs using the native REST ingestion API. This is the recommended method for VCF Operations for Logs integration — it uses the standard fluent-plugin-http (no additional gem required) and targets the stable /api/v2/events endpoint.
json_array true causes Fluentd to batch multiple records into a single JSON array per HTTP POST, which is required by the Operations for Logs v2 bulk ingest API.
The <inject> block adds a timestamp field in ISO-8601 format to each record so Operations for Logs can parse the event time correctly, rather than using the ingestion time.
| Setting | Value |
|---|---|
| Endpoint | http://192.168.200.40:9000/api/v2/events |
| Method | POST |
| Format | JSON array (bulk ingest) |
| Time field | timestamp (ISO-8601, injected per record) |
| TLS | disabled (tls_verify_mode none) |
| Flush interval | 10 seconds |
| Chunk size | 256 KB |
Note: HTTP on port 9000 must be explicitly enabled in the Operations for Logs appliance. Go to Configuration → SSL → API Server SSL and uncheck Require SSL Connection, then reboot the appliance.
To activate:
# fluent.conf
@include pods-fluentd.conf
@include operations-for-logs-fluentd.conf
To test the ingestion API directly:
# Step 1 — Obtain a session token
curl -sk -X POST https://192.168.200.40:9543/api/v2/sessions \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"<password>","provider":"Local"}' | jq
# Response:
# {
# "userId": "31530986-176b-41ca-b5e4-e47ae616787e",
# "sessionId": "SQadVha/u8PV2gwk...",
# "ttl": 1800
# }
# Step 2 — Generate a UUID (used as the agent/source identifier)
uuidgen
# e.g. 36a3891f-f6d4-4172-9b03-c769fa12ef78
# Step 3 — Send a test event over HTTPS (port 9543)
curl -sk -X POST \
https://192.168.200.40:9543/api/v2/events/ingest/36a3891f-f6d4-4172-9b03-c769fa12ef78 \
-H "Content-Type: application/json" \
-d '{"events":[{"text":"Test Event https"}]}' | jq
# Step 4 — Send a test event over HTTP (port 9000, after disabling SSL requirement)
curl -sk -X POST \
http://192.168.200.40:9000/api/v2/events/ingest/36a3891f-f6d4-4172-9b03-c769fa12ef78 \
-H "Content-Type: application/json" \
-d '{"events":[{"text":"Test Event http"}]}' | jq
# Step 5 — Query recent events (search for the last 5 minutes)
curl -sk -X POST https://192.168.200.40:9543/rest-api/internal/events/query \
-H "Authorization: Bearer <sessionId>" \
-H "Content-Type: application/json" \
-d '{"query":"WINDOWS TESTMSG","durationMs":300000}' | jqOperations for Logs UI navigation:
- Log Sources → Containers → Kubernetes — verify that Fluentd is appearing as a log source
- Configuration → SSL → API Server SSL — toggle HTTP access on port 9000
Type: output plugin
Gem: fluent-plugin-vmware-loginsight
File: loginsight-fluentd.conf
Deprecated.
fluent-plugin-vmware-loginsightis no longer maintained by VMware/Broadcom. Existing deployments continue to function, but new deployments should useoperations-for-logs-fluentd.confinstead.
<match **>
@type vmware_loginsight
host 192.168.200.40
port 9543
scheme https
http_method post
ssl_verify false
raise_on_error true
include_tag_key true
<buffer>
chunk_limit_records 300
flush_interval 3s
retry_max_times 3
</buffer>
</match>
The native plugin communicates with the Log Insight ingestion API using a proprietary format over HTTPS. It is kept here for backwards compatibility with deployments that were built before the HTTP plugin approach was recommended.
| Setting | Value |
|---|---|
| Host | 192.168.200.40 |
| Port | 9543 |
| Scheme | HTTPS |
| SSL verification | disabled |
| Chunk limit | 300 records |
| Flush interval | 3 seconds |
| Max retries | 3 (then drop) |
Unlike the elasticsearch plugin, this plugin does not retry forever — after 3 failed attempts it discards the buffer chunk. For production use, increase retry_max_times or switch to the HTTP plugin with retry_forever true.
counter-windows.yaml deploys a single pod to a Windows node. It runs a PowerShell loop that writes a numbered, timestamped line to stdout every 5 seconds.
kubectl apply -f counter-windows.yamlThe pod runs in the default namespace and requires the os=windows:NoSchedule toleration to be scheduled on the Windows node.
Live log view:
kubectl logs -f counter-windowsExpected stdout output:
0: WINDOWS TESTMSG 2024-01-15T10:23:45.1234567+00:00
1: WINDOWS TESTMSG 2024-01-15T10:23:50.1234567+00:00
2: WINDOWS TESTMSG 2024-01-15T10:23:55.1234567+00:00
After Fluentd collects and enriches these lines, each record delivered to the output backend will look like:
{
"@timestamp": "2024-01-15T10:23:45.123456789Z",
"namespace": "default",
"pod": "counter-windows",
"container": "count",
"node": "dev-cluster-05-win2022-ls7zk-vg5bz-fl6p6",
"log_type": "kubernetes",
"k8s_cluster": "dev-cluster-05",
"message": "42: WINDOWS TESTMSG 2024-01-15T10:23:45.1234567+00:00"
}This makes it easy to search for test records in any backend by filtering on pod: counter-windows or the literal string WINDOWS TESTMSG.
Remove the test pod when done:
kubectl delete -f counter-windows.yamlA single-node Elasticsearch 7.17.29 cluster and Kibana 7.17.29 are included to visualise the logs collected by Fluentd without requiring an external logging stack.
kubectl apply -f demo-elastic-kibana.yaml
kubectl apply -f demo-elastic-7.17.29.yaml
kubectl apply -f demo-kibana-7.17.29.yaml
kubectl -n elastic-kibana get pods,servicesExpected output once both pods are Running:
NAME READY STATUS RESTARTS AGE
pod/elasticsearch-6bf648d79d-j8md9 1/1 Running 0 4m17s
pod/kibana-78b4467cd4-hl4kh 1/1 Running 0 4m9s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
service/elasticsearch ClusterIP 10.105.70.170 <none> 9200/TCP
service/elasticsearch-lb LoadBalancer 10.105.70.171 192.168.231.11 9200:31xxx/TCP
service/kibana ClusterIP 10.106.92.41 <none> 5601/TCP
service/kibana-lb LoadBalancer 10.102.12.56 192.168.231.11 5601:32022/TCP
| Service | URL | Credentials |
|---|---|---|
| Kibana UI | http://<kibana-lb-external-ip>:5601 |
elastic / elastic |
| Elasticsearch | http://<elasticsearch-lb-external-ip>:9200 |
elastic / elastic |
Edit fluentd-daemonset-windows.yaml and set FLUENT_ELASTICSEARCH_HOST to the LoadBalancer external IP of the elasticsearch-lb service, then activate the Elasticsearch output plugin:
# fluent.conf
@include pods-fluentd.conf
@include elastic-fluentd.conf
Apply and restart:
kubectl apply -f fluentd-configmap-windows.yaml
kubectl -n fluentd rollout restart ds/fluentd- Open Kibana at
http://<kibana-lb-external-ip>:5601 - Navigate to Stack Management → Index Patterns → Create index pattern
- Enter
fluentd-windows-*as the pattern - Select
@timestampas the time field and click Create index pattern - Go to Discover to search and filter log records
# Check cluster health
curl -u elastic:elastic \
http://<elasticsearch-lb-external-ip>:9200/_cluster/health | jq
# List all fluentd indices with document counts
curl -u elastic:elastic \
"http://<elasticsearch-lb-external-ip>:9200/_cat/indices/fluentd-windows-*?v&s=index"
# Fetch the 5 most recent log records from all fluentd indices
curl -u elastic:elastic \
"http://<elasticsearch-lb-external-ip>:9200/fluentd-windows-*/_search?size=5&sort=@timestamp:desc&pretty"
# Search for counter-windows test messages specifically
curl -u elastic:elastic \
"http://<elasticsearch-lb-external-ip>:9200/fluentd-windows-*/_search?pretty" \
-H "Content-Type: application/json" \
-d '{"query":{"match":{"pod":"counter-windows"}},"size":10,"sort":[{"@timestamp":"desc"}]}'kubectl delete -f demo-kibana-7.17.29.yaml
kubectl delete -f demo-elastic-7.17.29.yaml
kubectl delete -f demo-elastic-kibana.yamlexport NAMESPACE=fluentd
FluentdPod=$(kubectl -n ${NAMESPACE} get pods --no-headers | grep fluentd | tail -1 | awk '{print $1}')
# Open a PowerShell session inside the Windows container
kubectl -n ${NAMESPACE} exec -it ${FluentdPod} -- powershellkubectl -n ${NAMESPACE} exec -it ${FluentdPod} -- powershell \
-Command "fluent-gem list | Select-String 'kubernetes|elastic|loginsight|http'"kubectl -n ${NAMESPACE} exec -it ${FluentdPod} -- powershell \
-Command "fluentd --dry-run -c C:\fluent\conf\fluent.conf -v"This is useful for seeing exactly what Fluentd is doing — every record parsed, every filter applied, every delivery attempt — without restarting the DaemonSet pod.
kubectl -n ${NAMESPACE} exec -it ${FluentdPod} -- powershell \
-Command "fluentd -c C:\fluent\conf\fluent.conf -vv"Directory: C:\fluent\etc
Mode LastWriteTime Name
---- ------------- ----
-a---l 2/28/2026 7:26 elastic-fluentd.conf
-a---l 2/28/2026 7:26 file-fluentd.conf
-a---l 2/28/2026 7:26 fluent.conf
-a---l 2/28/2026 7:26 loginsight-fluentd.conf
-a---l 2/28/2026 7:26 operations-for-logs-fluentd.conf
-a---l 2/28/2026 7:26 pods-fluentd.conf
# Container log symlinks — one symlink per container, named: <pod>_<ns>_<container>-<hash>.log
Get-ChildItem C:\var\log\containers\
# antrea-agent-windows-kqrxh_kube-system_antrea-agent-a001018663fa...log
# counter-windows_default_count-<hash>.log
# fluentd-njtlw_fluentd_fluentd-2e4683425ef32...log
# Pod log directories — one directory per pod, containing per-container subdirectories
Get-ChildItem C:\var\log\pods\
# fluentd_fluentd-njtlw_acc7aa7e-e12d-49f9-8a91-2423d92af123\
# kube-system_antrea-agent-windows-kqrxh_f0a4c9b0-58eb-4308-...\
# vmware-system-csi_vsphere-csi-node-windows-cx96w_349cb0fd-...\
# Fluentd position file and file-output logs
Get-ChildItem C:\var\log\fluentd\If Fluentd or demo stack pods are rejected by the pod-security admission controller, label all namespaces as privileged:
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
kubectl label ns "$ns" \
pod-security.kubernetes.io/enforce=privileged \
pod-security.kubernetes.io/audit=privileged \
pod-security.kubernetes.io/warn=privileged \
--overwrite
done- Fluentd Kubernetes DaemonSet — Docker Hub
- Fluentd on Windows Kubernetes (bgsilvait)
- On-Premise Windows Kubernetes Logging with IIS, Fluentd, and Elasticsearch
- Using Elasticsearch/Fluentd/Kibana for Windows container logging
- Fluentd on Kubernetes: Log collection explained (YouTube)
- fluent-plugin-kubernetes_metadata_filter
- fluent-plugin-elasticsearch
- Fluentd HTTP output plugin
- VMware Aria Operations for Logs REST API
- fluent-plugin-vmware-loginsight deprecation notice