ClusterLogForwarder on OpenShift

Troy Heber

807 READ TIME 3 Minutes, 40 Seconds

2025-09-15 18:00 -0600


Introduction

The goal of this article is to present the information necessary to demonstrate cluster log forwarding from an OpenShift cluster using the ClusterLogForwarder functionality based on the Vector collector from Red Hat OpenShift Logging 6.x.

The OpenShift Logging Operator provides the ClusterLogging and ClusterLogForwarder CRs. The latter provides the ability to forward logs to external destinations. Logs are not stored on the OpenShift cluster by default. The optional Loki operator with access to object storage is used to provide log storage functionality.

Prerequisites

  • An OpenShift 4.x cluster
  • A receiver that can ingest the forwarded logs

Running VictoriaLogs

Most likely you will already have a service in the environment that will ingest the logs that are forwarded from OpenShift. However, if you are looking to test this functionality in a lab environment you might be looking for a quick and dirty solution to receive the logs to validate your pipeline configuration. One such option is to use VictoriaLogs.

There are multiple options for deploying VictoriaLogs but the easiest is to simply run the container.

Create a folder to hold the collected logs

mkdir /data/victoria-logs-data

Run the pod

podman run --name victoria-logs --rm -it -p 9428:9428 -v /data/victoria-logs-data:/victoria-logs-data:Z docker.io/victoriametrics/victoria-logs:v1.26.0 -storageDataPath=victoria-logs-data

Use Podlet to generate Podman Quadlet files

There is a great utility called Podlet that will transform a podman run command into the proper syntax for the systemd unit file. You can download the podlet binary or just use podman to run it as a container without downloading and installing:

podman run ghcr.io/containers/podlet --install podman run --name victoria-logs --rm -it -p 9428:9428 -v /data/victoria-logs-data:/victoria-logs-data:Z  docker.io/victoriametrics/victoria-logs:v1.26.0 -storageDataPath=victoria-logs-data

The output should look something like this:

victoria-logs.container

[Container]
ContainerName=victoria-logs
Exec='-storageDataPath=victoria-logs-data'
Image=docker.io/victoriametrics/victoria-logs:v1.26.0
PodmanArgs=--interactive --tty
PublishPort=9428:9428
Volume=/data/victoria-logs-data:/victoria-logs-data:Z

[Install]
WantedBy=default.target

Save that above output to a file: /usr/share/containers/systemd/victoria-logs.container

Use systemd to control the new service

sudo systemctl daemon-reload
sudo systemctl start victoria-logs.service
sudo systemctl status victoria-logs.service

Query the logs

Logs stored in VictoriaLogs can be queried at the /select/logsql/query HTTP endpoint. The LogsQL query must be passed via query argument. For example, to query all of the logs:

curl http://<hostname>:9428/select/logsql/query -d 'query=*'|jq '.'

Live tailing

# All logs
curl http://<hostname>:9428/select/logsql/tail -d 'query=*'|jq '.'
# LogsQL to tail on a specific host
curl http://<hostname>:9428/select/logsql/tail -d 'query=hostname:"<my_FQDN>"'|jq '.'

Setup OpenShift ClusterLogForwarder

The configuration below will start the pipeline using the default inputRefs for application and infrastructure. Next the pipeline will apply two different filters, one for adding additional labels to the logs and one to drop all messages that do not have the level of critical, error, or warning.

The add-labels “filter” is being used to add the labels for cluster_name and cluster_type.

The output uses the Elasticsearch sink type and the URL specifies the HTTP Query string parameters for _msg_field, _time_field and _stream_fields.

apiVersion: observability.openshift.io/v1
kind: ClusterLogForwarder
metadata:
  annotations:
    observability.openshift.io/tech-preview-otlp-output: enabled
  name: logging-forwarder-victora-logs
  namespace: openshift-logging
spec:
  collector:
    resources:
      limits:
        memory: 32Gi
  filters:
    - name: add-labels
      openshiftLabels:
        cluster_name: sam
        cluster_type: ocp-virt
      type: openshiftLabels
    - drop:
        - test:
            - field: .level
              notMatches: (?i)critical|error|warning
      name: keep-critical
      type: drop
  managementState: Managed
  outputs:
    - elasticsearch:
        index: ocp-virt
        url: http://elmer.example.com:9428/insert/elasticsearch/_bulk?_stream_fields=log_type,hostname,stream,kubernetes.pod_name,kubernetes.container_name,kubernetes.pod_namespace&_time_field=@timestamp&_msg_field=message,msg,_msg,log.msg,log.message,log&fake_field=1
        version: 8
      name: elasticsearch-output
      type: elasticsearch
  pipelines:
    - filterRefs:
        - keep-critical
        - add-labels
      inputRefs:
        - application
        - infrastructure
      name: logs-to-es
      outputRefs:
        - elasticsearch-output
  serviceAccount:
    name: collector

VictoriaLogs

Here is an example of the raw JSON of a log queried from VictoriaLogs using the “All logs” query from above.

{
  "_time": "2025-10-27T18:12:29.894389919Z",
  "_stream_id": "0000000000000000105e188d66f8ce2d2682acf51e42ea2a",
  "_stream": "{hostname=\"sam.example.com\",kubernetes.container_name=\"manager\",kubernetes.pod_name=\"vault-secrets-operator-controller-manager-769bf97ff-mfch6\",log_type=\"infrastructure\"}",
  "_msg": "{\"level\":\"error\",\"ts\":\"2025-10-27T18:12:29Z\",\"logger\":\"cachingClientFactory\",\"msg\":\"Failed to get NewClientWithLogin\",\"controller\":\"vaultstaticsecret\",\"controllerGroup\":\"secrets.hashicorp.com\",\"controllerKind\":\"VaultStaticSecret\",\"VaultStaticSecret\":{\"name\":\"vss-etcd-backup\",\"namespace\":\"openshift-etcd-backup\"},\"namespace\":\"openshift-etcd-backup\",\"name\":\"vss-etcd-backup\",\"reconcileID\":\"8ec961f7-85c7-40f6-9ccc-9660dac6a9f1\",\"cacheKey\":\"approle-9b008cbf5ce63e51f52c70\",\"error\":\"Error making API request.\\n\\nNamespace: openshift-config\\nURL: PUT https://vault.example.com:8200/v1/auth/vso/login\\nCode: 503. Errors:\\n\\n* Vault is sealed\",\"stacktrace\":\"github.com/hashicorp/vault-secrets-operator/vault.(*cachingClientFactory).Get\\n\\t/home/runner/work/vault-secrets-operator/vault-secrets-operator/vault/client_factory.go:483\\ngithub.com/hashicorp/vault-secrets-operator/controllers.(*VaultStaticSecretReconciler).Reconcile\\n\\t/home/runner/work/vault-secrets-operator/vault-secrets-operator/controllers/vaultstaticsecret_controller.go:94\\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Reconcile\\n\\t/home/runner/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.22.1/pkg/internal/controller/controller.go:216\\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler\\n\\t/home/runner/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.22.1/pkg/internal/controller/controller.go:461\\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem\\n\\t/home/runner/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.22.1/pkg/internal/controller/controller.go:421\\nsigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func1.1\\n\\t/home/runner/go/pkg/mod/sigs.k8s.io/controller-runtime@v0.22.1/pkg/internal/controller/controller.go:296\"}",
  "hostname": "sam.example.com",
  "kubernetes.annotations.capabilities": "Seamless Upgrades",
  "kubernetes.annotations.categories": "Security",
  "kubernetes.annotations.containerImage": "registry.connect.redhat.com/hashicorp/vault-secrets-operator@sha256:84ac7003c4cb90c35f1e296585bb58187a86d64f972133e2661839bdb532ddb8",
  "kubernetes.annotations.createdAt": "2025-09-26T21:50:51Z",
  "kubernetes.annotations.description": "The Vault Secrets Operator (VSO) allows Pods to consume Vault secrets natively from Kubernetes Secrets.",
  "kubernetes.annotations.features.operators.openshift.io/cnf": "false",
  "kubernetes.annotations.features.operators.openshift.io/cni": "false",
  "kubernetes.annotations.features.operators.openshift.io/csi": "false",
  "kubernetes.annotations.features.operators.openshift.io/disconnected": "true",
  "kubernetes.annotations.features.operators.openshift.io/fips-compliant": "false",
  "kubernetes.annotations.features.operators.openshift.io/proxy-aware": "true",
  "kubernetes.annotations.features.operators.openshift.io/tls-profiles": "false",
  "kubernetes.annotations.features.operators.openshift.io/token-auth-aws": "false",
  "kubernetes.annotations.features.operators.openshift.io/token-auth-azure": "false",
  "kubernetes.annotations.features.operators.openshift.io/token-auth-gcp": "false",
  "kubernetes.annotations.k8s.v1.cni.cncf.io/network-status": "[{\n    \"name\": \"ovn-kubernetes\",\n    \"interface\": \"eth0\",\n    \"ips\": [\n        \"10.128.0.195\"\n    ],\n    \"mac\": \"0a:58:0a:80:00:c3\",\n    \"default\": true,\n    \"dns\": {}\n}]",
  "kubernetes.annotations.kubectl.kubernetes.io/default-container": "manager",
  "kubernetes.annotations.olm.operatorGroup": "global-operators",
  "kubernetes.annotations.olm.operatorNamespace": "openshift-operators",
  "kubernetes.annotations.openshift.io/scc": "restricted-v2",
  "kubernetes.annotations.operators.operatorframework.io/builder": "operator-sdk-v1.33.0",
  "kubernetes.annotations.operators.operatorframework.io/project_layout": "go.kubebuilder.io/v3",
  "kubernetes.annotations.repository": "https://github.com/hashicorp/vault-secrets-operator",
  "kubernetes.annotations.seccomp.security.alpha.kubernetes.io/pod": "runtime/default",
  "kubernetes.annotations.support": "HashiCorp",
  "kubernetes.container_id": "cri-o://1f8fef8ce9b813a725bf664253ebf3a1cd7d52cd323d1d25c9f09d106f5b84e6",
  "kubernetes.container_image": "registry.connect.redhat.com/hashicorp/vault-secrets-operator@sha256:84ac7003c4cb90c35f1e296585bb58187a86d64f972133e2661839bdb532ddb8",
  "kubernetes.container_image_id": "registry.connect.redhat.com/hashicorp/vault-secrets-operator@sha256:56251cd14b99a6078edf44ba01c2fbd42d123466c5efa24911fed0566e8a876e",
  "kubernetes.container_iostream": "stderr",
  "kubernetes.container_name": "manager",
  "kubernetes.labels.app_kubernetes_io_component": "controller-manager",
  "kubernetes.labels.control-plane": "controller-manager",
  "kubernetes.labels.pod-template-hash": "769bf97ff",
  "kubernetes.namespace_id": "1e159892-99c2-4547-931c-a0026db8824c",
  "kubernetes.namespace_labels.kubernetes_io_metadata_name": "openshift-operators",
  "kubernetes.namespace_labels.openshift_io_cluster-monitoring": "true",
  "kubernetes.namespace_labels.pod-security_kubernetes_io_audit": "restricted",
  "kubernetes.namespace_labels.pod-security_kubernetes_io_audit-version": "latest",
  "kubernetes.namespace_labels.pod-security_kubernetes_io_enforce": "privileged",
  "kubernetes.namespace_labels.pod-security_kubernetes_io_enforce-version": "latest",
  "kubernetes.namespace_labels.pod-security_kubernetes_io_warn": "restricted",
  "kubernetes.namespace_labels.pod-security_kubernetes_io_warn-version": "latest",
  "kubernetes.namespace_labels.security_openshift_io_scc_podSecurityLabelSync": "true",
  "kubernetes.namespace_name": "openshift-operators",
  "kubernetes.pod_id": "c45ee4cd-8f53-4b9f-a479-6ed318b0eedf",
  "kubernetes.pod_ip": "10.128.0.195",
  "kubernetes.pod_name": "vault-secrets-operator-controller-manager-769bf97ff-mfch6",
  "kubernetes.pod_owner": "ReplicaSet/vault-secrets-operator-controller-manager-769bf97ff",
  "level": "error",
  "log_source": "container",
  "log_type": "infrastructure",
  "openshift.cluster_id": "71eae85f-f749-4bbc-a825-8417db701e4b",
  "openshift.labels.cluster_name": "sam",
  "openshift.labels.cluster_type": "ocp-virt",
  "kubernetes.annotations.alm-examples": "[\n  {\n    \"apiVersion\": \"secrets.hashicorp.com/v1beta1\",\n    \"kind\": \"VaultConnection\",\n    \"metadata\": {\n      \"name\": \"vaultconnection-sample\",\n      \"namespace\": \"tenant-1\"\n    },\n    \"spec\": {\n      \"address\": \"http://vault.vault.svc.cluster.local:8200\"\n    }\n  },\n  {\n    \"apiVersion\": \"secrets.hashicorp.com/v1beta1\",\n    \"kind\": \"VaultAuth\",\n    \"metadata\": {\n      \"name\": \"vaultauth-sample\",\n      \"namespace\": \"tenant-1\"\n    },\n    \"spec\": {\n      \"vaultConnectionRef\": \"vaultconnection-sample\",\n      \"method\": \"kubernetes\",\n      \"mount\": \"kubernetes\",\n      \"kubernetes\": {\n        \"role\": \"sample\",\n        \"serviceAccount\": \"default\"\n      }\n    }\n  },\n  {\n    \"apiVersion\": \"secrets.hashicorp.com/v1beta1\",\n    \"kind\": \"VaultStaticSecret\",\n    \"metadata\": {\n      \"name\": \"vaultstaticsecret-sample\",\n      \"namespace\": \"tenant-1\"\n    },\n    \"spec\": {\n      \"vaultAuthRef\": \"vaultauth-sample\",\n      \"mount\": \"kvv2\",\n      \"type\": \"kv-v2\",\n      \"path\": \"secret\",\n      \"refreshAfter\": \"5s\",\n      \"destination\": {\n        \"create\": true,\n        \"name\": \"secret1\"\n      }\n    }\n  }\n]",
  "kubernetes.annotations.k8s.ovn.org/pod-networks": "{\"default\":{\"ip_addresses\":[\"10.128.0.195/23\"],\"mac_address\":\"0a:58:0a:80:00:c3\",\"gateway_ips\":[\"10.128.0.1\"],\"routes\":[{\"dest\":\"10.128.0.0/14\",\"nextHop\":\"10.128.0.1\"},{\"dest\":\"172.30.0.0/16\",\"nextHop\":\"10.128.0.1\"},{\"dest\":\"169.254.0.5/32\",\"nextHop\":\"10.128.0.1\"},{\"dest\":\"100.64.0.0/16\",\"nextHop\":\"10.128.0.1\"}],\"ip_address\":\"10.128.0.195/23\",\"gateway_ip\":\"10.128.0.1\",\"role\":\"primary\"}}",
  "kubernetes.annotations.operatorframework.io/properties": "{\"properties\":[{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"CSISecrets\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"HCPAuth\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"HCPVaultSecretsApp\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"SecretTransformation\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"VaultAuth\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"VaultAuthGlobal\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"VaultConnection\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"VaultDynamicSecret\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"VaultPKISecret\",\"version\":\"v1beta1\"}},{\"type\":\"olm.gvk\",\"value\":{\"group\":\"secrets.hashicorp.com\",\"kind\":\"VaultStaticSecret\",\"version\":\"v1beta1\"}},{\"type\":\"olm.package\",\"value\":{\"packageName\":\"vault-secrets-operator\",\"version\":\"1.0.1\"}}]}",
  "openshift.sequence": "1761588749944941137",
  "timestamp": "2025-10-27T18:12:29.894389919Z"
}