Skip to content

Replace fluentd with fluent-bit in the operator#4910

Open
hjiawei wants to merge 8 commits into
tigera:masterfrom
hjiawei:fluent-bit-deploy
Open

Replace fluentd with fluent-bit in the operator#4910
hjiawei wants to merge 8 commits into
tigera:masterfrom
hjiawei:fluent-bit-deploy

Conversation

@hjiawei

@hjiawei hjiawei commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Description

New feature (Calico Enterprise): the LogCollector controller now deploys fluent-bit (calico-fluent-bit in calico-system) in place of fluentd, completing the log-collector migration on the operator side.

  • Resource identity migration: namespace tigera-fluentdcalico-system; DaemonSet/ServiceAccount fluentd-nodecalico-fluent-bit; TLS secret → calico-fluent-bit-tls; metrics port 9081 → 2020 (fluent-bit's built-in HTTP server).
  • Configuration is rendered in fluent-bit's YAML schema into per-OS ConfigMaps (calico-fluent-bit-conf/-windows), subPath-mounted on Linux and directory-mounted on Windows, started with -c. The render loads the Go plugins via plugins_file, defines parsers inline, applies the record-transform Lua filter, and inlines user-provided fluent-bit YAML filter lists. A rendered-config hash annotation rolls the pods on config-only changes.
  • Tail inputs use the producing components' real log paths, with SQLite offset DBs and filesystem buffering under /var/log/calico/calico-fluent-bit; a pos-migrator init container (Linux and Windows) seeds offsets from the legacy fluentd .pos files and pre-creates the tailed directories. Windows tails the same log types the fluentd Windows variant shipped.
  • The linseed output matches only Linseed-bound tags, authenticates with mTLS + the pod's ServiceAccount token, and retries without limit against bounded filesystem storage. S3/Splunk/Syslog outputs mirror fluentd's per-type fan-out (standard AWS credential env vars, endpoint scheme honored, syslog ships the whole record as JSON via a per-output Lua processor with TLS properly enabled).
  • NonClusterHost renders the :9880 HTTP input with client-certificate verification; the input Service is cleaned up when the resource is removed.
  • eks-log-forwarder runs the fluent-bit image with a rendered in_ekslinseed pipeline and health probes (no startup init container; the input plugin resolves its resume point from Linseed).
  • Probes hit :2020/api/v1/health; the ServiceMonitor scrapes plain HTTP (fluent-bit's monitoring server has no TLS) with access restricted by the component NetworkPolicy, and legacy fluentd monitors are deleted.
  • The LogCollector controller no longer creates/owns the calico-system namespace (deleting the LogCollector must not garbage-collect it); the deprecated fluentdDaemonSet override is honored as an alias of the new calicoFluentBitDaemonSet field (with container-name translation); legacy tigera-fluentd resources — the namespace last — are cleaned up idempotently.

Testing: render, controller, and monitor unit suites updated/extended (ConfigMap-content assertions replacing the env-var assertions); the rendered configuration was validated against the real fluent-bit binary; the full migration was validated end-to-end on a test cluster — all log types flowing to Linseed/Elasticsearch, fluentd resources fully removed, tail-offset handover without re-shipping, NonClusterHost ingestion with client-certificate enforcement, and EKS/Windows render shapes verified.

Release Note

The LogCollector now deploys fluent-bit (calico-fluent-bit in calico-system) in place of fluentd, with operator-rendered configuration and automatic migration of fluentd tail positions.

Breaking changes and behavior changes in this migration:
- The tigera-fluentd namespace is removed; all log-collector resources are renamed and move to calico-system (DaemonSet/container calico-fluent-bit, TLS secret calico-fluent-bit-tls). Tooling referencing the old names (e.g. kubectl logs -c fluentd) must be updated. The LogCollector fluentdDaemonSet override field is deprecated in favor of calicoFluentBitDaemonSet; existing overrides (including legacy container names) keep applying for one release.
- User flow/DNS log filters: the fluentd-filters ConfigMap is no longer read. Filters must be recreated under the new fluent-bit-filters name as fluent-bit YAML filter lists; unparseable content raises a TigeraStatus warning while log shipping continues.
- Log-collector metrics are now fluent-bit's native Prometheus metrics (fluentbit_* metric names, plain HTTP on port 2020 at /api/v2/metrics/prometheus, guarded by NetworkPolicy). Dashboards and alerts keyed on fluentd_* metric names or the 9081 mTLS endpoint must be updated.
- S3 archive object keys change from fluentd's flat layout (<bucketPath>/flows20260101_<n>.gz) to directory-style keys (<bucketPath>/flows/20260101_<uuid>.gz), with non-cluster-host flows archived under their own non_cluster_flows/ directory. Downstream tooling anchored to the old flat patterns needs a one-time update.
- Log buffering moves from fluentd's in-memory buffers to fluent-bit filesystem storage under /var/log/calico/calico-fluent-bit/ on each node: buffered-but-unsent chunks now survive pod restarts, and host disk usage grows accordingly (capped per output by storage.total_limit_size).
- The direct Elasticsearch log output is removed (Linseed is the sole structured-log sink), and the Splunk output no longer has an L7 log type (deployed fluentd never enabled it).
- Non-cluster hosts can now forward DNS logs in addition to flow and policy-activity logs.

For PR author

  • Tests for change.
  • If changing pkg/apis/, run make gen-files
  • If changing versions, run make gen-versions

For PR reviewers

A note for code reviewers - all pull requests must have the following:

  • Milestone set according to targeted release.
  • Appropriate labels:
    • kind/bug if this is a bugfix.
    • kind/enhancement if this is a a new feature.
    • enterprise if this PR applies to Calico Enterprise only.

@marvin-tigera marvin-tigera added this to the v1.43.0 milestone Jun 10, 2026
@hjiawei hjiawei added kind/enhancement New feature or request enterprise Feature applies to enterprise only labels Jun 10, 2026
@hjiawei hjiawei force-pushed the fluent-bit-deploy branch 2 times, most recently from e121ac4 to c81ad06 Compare June 11, 2026 20:04
@radTuti radTuti modified the milestones: v1.43.0, v1.44.0 Jun 12, 2026
@hjiawei hjiawei force-pushed the fluent-bit-deploy branch from c81ad06 to ff46afe Compare June 25, 2026 15:17
@hjiawei hjiawei marked this pull request as ready for review June 26, 2026 16:43
@hjiawei hjiawei requested review from a team and marvin-tigera as code owners June 26, 2026 16:43
@hjiawei hjiawei requested a review from Copilot June 26, 2026 16:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR completes the operator-side migration of the LogCollector from fluentd to fluent-bit (Calico Enterprise), including updated resource identities (namespace/workload names), rendered configuration shape, and updated policy/monitoring expectations across the rendering and controllers.

Changes:

  • Replace fluentd-specific rendering/controller wiring with fluent-bit equivalents (names, ports, RBAC, ServiceMonitor behavior).
  • Update expected Calico policies/selectors to reflect fluent-bit running in calico-system and using port 2020.
  • Extend the LogCollector CRD/API to add calicoFluentBitDaemonSet and treat the deprecated fluentdDaemonSet as an alias during migration.

Reviewed changes

Copilot reviewed 47 out of 48 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
pkg/render/tiers/tiers_test.go Updates tier rendering tests to stop expecting a separate LogCollector namespace.
pkg/render/testutils/expected_policies/node_local_dns_ipv6.json Removes legacy tigera-fluentd namespace from node-local-dns selector expectations.
pkg/render/testutils/expected_policies/node_local_dns_ipv4.json Removes legacy tigera-fluentd namespace from node-local-dns selector expectations.
pkg/render/testutils/expected_policies/node_local_dns_dual.json Removes legacy tigera-fluentd namespace from node-local-dns selector expectations.
pkg/render/testutils/expected_policies/linseed.json Updates Linseed ingress policy sources from fluentd labels/namespace to fluent-bit in calico-system.
pkg/render/testutils/expected_policies/linseed_ocp.json Same as above for OpenShift policy expectations.
pkg/render/testutils/expected_policies/linseed_ocp_dpi_enabled.json Same as above for OpenShift + DPI-enabled scenario.
pkg/render/testutils/expected_policies/linseed_dpi_enabled.json Same as above for DPI-enabled scenario.
pkg/render/testutils/expected_policies/guardian.json Updates Guardian policy expectations to allow fluent-bit instead of fluentd.
pkg/render/testutils/expected_policies/guardian_ocp.json Same as above for OpenShift policy expectations.
pkg/render/testutils/expected_policies/fluentbit_unmanaged.json Updates fluent-bit “allow metrics” policy expectations (name/namespace/selector/port).
pkg/render/testutils/expected_policies/fluentbit_unmanaged_ocp.json Same as above for OpenShift.
pkg/render/testutils/expected_policies/fluentbit_managed.json Updates managed-cluster policy expectations for fluent-bit metrics access.
pkg/render/testutils/expected_policies/es-gateway.json Updates ES gateway ingress policy sources from fluentd to fluent-bit.
pkg/render/testutils/expected_policies/es-gateway_ocp.json Same as above for OpenShift.
pkg/render/testutils/expected_policies/dns.json Removes legacy tigera-fluentd namespace from DNS policy selector expectations.
pkg/render/testutils/expected_policies/dns_ocp.json Same as above for OpenShift DNS policy expectations.
pkg/render/nonclusterhost/nonclusterhost.go Adds Linseed RBAC permissions for DNS logs in non-cluster-host flow.
pkg/render/nonclusterhost/nonclusterhost_test.go Updates RBAC test expectations to include dnslogs.
pkg/render/monitor/monitor.go Renames fluentd monitoring constants to fluent-bit and switches ServiceMonitor to plain HTTP fluent-bit metrics endpoint.
pkg/render/monitor/monitor_test.go Updates monitor rendering tests for renamed ServiceMonitor and the removal of TLS/scheme relabeling.
pkg/render/manager.go Updates manager-rendered NetworkPolicy destination service name and cluster-wide namespace list (includes a problematic entry noted in comments).
pkg/render/manager_test.go Updates manager tests to expect fluent-bit service names.
pkg/render/logstorage/linseed/linseed.go Updates Linseed policy source entity rule reference to fluent-bit.
pkg/render/logstorage/esgateway/esgateway.go Updates ES gateway policy source entity rule reference to fluent-bit.
pkg/render/intrusion_detection.go Updates comments to reflect fluent-bit consuming IDS logs.
pkg/render/guardian.go Updates Guardian policy source entity rule reference to fluent-bit.
pkg/render/fluentd.go Removes the fluentd renderer implementation.
pkg/render/fluentd_test.go Removes fluentd rendering unit tests.
pkg/render/common/elasticsearch/service.go Renames isFluentd flag to isFluentBit and updates related commentary for managed-cluster Linseed routing.
pkg/render/applicationlayer/applicationlayer.go Updates comment about WAF logs being consumed by fluent-bit.
pkg/imports/crds/operator/operator.tigera.io_logcollectors.yaml Adds calicoFluentBitDaemonSet and updates fluentdDaemonSet docs/enums for the migration (some descriptions still say “Fluentd”, noted in comments).
pkg/controller/tiers/tiers_controller.go Stops treating LogCollector as its own namespace when computing tier namespace lists.
pkg/controller/monitor/prometheus.go Watches fluent-bit ServiceMonitor name instead of fluentd.
pkg/controller/monitor/monitor_controller.go Switches monitored TLS secret from fluentd to fluent-bit.
pkg/controller/monitor/monitor_controller_test.go Updates controller tests to reference fluent-bit ServiceMonitor and secrets.
pkg/controller/logstorage/secrets/secret_controller.go Updates upstream cert collection references from fluentd TLS secret to fluent-bit TLS secret.
pkg/controller/logstorage/secrets/secret_controller_test.go Updates tests to create/check fluent-bit TLS secret scenarios.
pkg/controller/logcollector/logcollector_controller.go Rewires controller from fluentd to fluent-bit rendering (secrets, filters, services, namespace ownership behavior).
pkg/controller/logcollector/logcollector_controller_test.go Updates controller tests for fluent-bit DaemonSet naming, config-driven outputs, and non-creation of calico-system.
pkg/components/enterprise.go Renames enterprise component definitions from fluentd to fluent-bit (linux/windows).
hack/gen-versions/enterprise.go.tpl Updates version generation template keys/structs for fluent-bit components.
config/enterprise_versions.yml Replaces fluentd component entries with fluent-bit equivalents.
api/v1/zz_generated.deepcopy.go Adds deepcopy support for CalicoFluentBitDaemonSet on LogCollectorSpec.
api/v1/logcollector_types.go Adds calicoFluentBitDaemonSet field and documents fluentdDaemonSet as deprecated alias.
api/v1/fluentd_daemonset_types.go Updates override container/initContainer enums to fluent-bit naming (backward-compat concerns noted in comments).
Files not reviewed (1)
  • api/v1/zz_generated.deepcopy.go: Generated file

Comment thread api/v1/fluentd_daemonset_types.go Outdated
Comment thread api/v1/fluentd_daemonset_types.go Outdated
Comment thread pkg/render/manager.go
Comment thread pkg/imports/crds/operator/operator.tigera.io_logcollectors.yaml Outdated
Comment thread pkg/imports/crds/operator/operator.tigera.io_logcollectors.yaml Outdated
@hjiawei hjiawei force-pushed the fluent-bit-deploy branch 2 times, most recently from 2403d11 to 98e7752 Compare June 29, 2026 18:10
@hjiawei hjiawei requested a review from Copilot June 29, 2026 19:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 49 out of 50 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • api/v1/zz_generated.deepcopy.go: Generated file
Comments suppressed due to low confidence (2)

pkg/controller/monitor/monitor_controller.go:156

  • Monitor no longer scrapes fluent-bit over mTLS (serviceMonitorFluentBit is plain HTTP), so the controller shouldn't watch or require the fluent-bit TLS secret. Keeping it here creates an unnecessary dependency on the LogCollector controller and can degrade Monitor reconciliation when LogCollector isn't installed (secret not found).
	for _, secret := range []string{
		certificatemanagement.CASecretName,
		esmetrics.ElasticsearchMetricsServerTLSSecret,
		monitor.PrometheusServerTLSSecretName,
		render.FluentBitTLSSecretName,
		render.NodePrometheusTLSServerSecret,
		kubecontrollers.KubeControllerPrometheusTLSSecret,
		render.EKSLogForwarderTLSSecretName,
	} {

pkg/controller/monitor/monitor_controller.go:345

  • Since fluent-bit metrics scraping is now plain HTTP, Monitor's trusted bundle no longer needs the fluent-bit certificate. Including it makes reconciliation fail with NotFound if LogCollector hasn't created the secret yet (or isn't installed).
	for _, certificateName := range []string{
		esmetrics.ElasticsearchMetricsServerTLSSecret,
		render.FluentBitTLSSecretName,
		render.NodePrometheusTLSServerSecret,
		render.CalicoAPIServerTLSSecretName,
		kubecontrollers.KubeControllerPrometheusTLSSecret,
	} {

Comment thread pkg/controller/monitor/prometheus.go
@hjiawei hjiawei force-pushed the fluent-bit-deploy branch from 98e7752 to 51596b3 Compare June 29, 2026 21:10
Comment thread api/v1/logcollector_types.go
@hjiawei hjiawei force-pushed the fluent-bit-deploy branch from f0561ca to 473c516 Compare July 1, 2026 22:55
hjiawei added a commit to hjiawei/tigera-docs that referenced this pull request Jul 2, 2026
…-6164)

Calico Enterprise removes the fluentd log collector in favour of Fluent Bit
(calico-fluent-bit in calico-system; tigera/operator#4910). Update the
next-version Calico Enterprise and Calico Cloud docs to match:

- Rewrite "Filter flow logs" / "Filter DNS logs": examples move from
  fluentd <filter> syntax to Fluent Bit YAML filter lists, the ConfigMap
  is renamed fluentd-filters -> fluent-bit-filters, and an upgrade note
  explains that old fluentd-syntax filters are not translated (the
  operator raises a tigera status warning naming the offending key).
- Metrics pages: Fluent Bit's built-in Prometheus endpoint (port 2020,
  /api/v2/metrics/prometheus, fluentbit_* metrics); the fluentd
  buffer-space alert becomes fluentbit_output_chunk_available_capacity_percent
  plus a dropped-chunks alert on fluentbit_output_retries_failed_total.
- BYO Prometheus: the fluent-bit tab needs no client TLS (plain HTTP
  behind the allow-calico-fluent-bit policy); ServiceMonitor sample is
  fluent-bit-metrics-service-monitor.yaml.
- Sweep of remaining pages: tigera-fluentd namespace -> calico-system,
  fluentd-node -> calico-fluent-bit (labels, secrets, packet capture
  retrieval via calico-node, diags output, resource override examples now
  use calicoFluentBitDaemonSet, architecture/overview descriptions,
  cc-arch-diagram ports, operator checklist).

Not touched: versioned docs, releases.json (historical), and the generated
installation API reference (regenerates from the operator release).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hjiawei hjiawei force-pushed the fluent-bit-deploy branch from 473c516 to 0c9d6bc Compare July 7, 2026 00:47
if err = utils.AddConfigMapWatch(c, configMapName, common.OperatorNamespace(), &handler.EnqueueRequestForObject{}); err != nil {
return fmt.Errorf("logcollector-controller failed to watch ConfigMap %s: %v", configMapName, err)
}
if err = utils.AddConfigMapWatch(c, rlogcollector.FluentBitFilterConfigMapName, common.OperatorNamespace(), &handler.EnqueueRequestForObject{}); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider watching the configmap we render for fluent-bit if we want to prevent users from tampering with it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — the controller now watches the rendered calico-fluent-bit-conf, calico-fluent-bit-conf-windows and eks-log-forwarder-conf ConfigMaps in calico-system, so tampering (or deletion) triggers reconciliation and the operator restores the rendered content.

Comment on lines +690 to +700
// Must match the Linux configuration: both OS variants render the
// shared allow-calico-fluent-bit NetworkPolicy, whose non-cluster-host
// ingress rule (port 9880) is gated on NonClusterHost and whose
// manager source selector is tenant-aware. If only the Linux config
// sets these, the two renders disagree and the operator flaps the
// policy on every reconcile (dropping voltron's access to the http
// input). The non-cluster-host input/service themselves are still
// Linux-only (gated on OSType), so NonClusterHost only affects the
// policy; Tenant/ExternalElastic additionally select the Linseed
// endpoint and x-tenant-id header in the Windows rendered config.
NonClusterHost: nonclusterhost,

@pasanw pasanw Jul 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should extract out a component that renders out the resources that are shared between the linux and windows instances of this component. As I understand it, some resources are unique to the specific instance via OS naming convention.

However, some resources are not unique. This means that each instance of the component needs to compute the same definition of such resources, otherwise we have contention like you described. This pattern is confusing and seems like it could be the cause bugs in the future like the one you described

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added logcollector.FluentBitShared, rendered exactly once per reconcile, owning everything that is not OS-specific: the allow-calico-fluent-bit NetworkPolicy, the GKE ResourceQuota, the S3/Splunk credential copies, the managed-cluster tigera-linseed Service/RoleBinding, the PacketCapture Role/Binding, and the legacy-fluentd cleanup. The per-OS components now render only their own metrics Service, conf ConfigMap, RBAC/SA, DaemonSet (+ EKS forwarder and the NCH input service on Linux). That removes the render-the-same-object-twice contention class outright — the Windows cfg no longer needs NonClusterHost at all, and there's a test asserting the Linux and Windows renders produce disjoint object sets.

AdditionalStores: &operatorv1.AdditionalLogStoreSpec{
Syslog: &operatorv1.SyslogStoreSpec{
Endpoint: "https://localhost:1234",
Endpoint: "tcp://localhost:1234",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does fluent-bit require tcp:// instead of http:// or https://? If so should we enforce this on the user input?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — fluent-bit's syslog mode only accepts tcp/udp (TLS comes from the encryption field, which selects mode tls on top of tcp framing), and an http:// endpoint would have rendered a broken output. The controller now rejects non-tcp/udp schemes with a ResourceValidationError degraded status, with a test. (Same behavior fluentd had implicitly via SYSLOG_PROTOCOL.)

common.CalicoNamespace,
}
if r.opts.EnterpriseCRDExists {
// The log collector (fluent-bit) runs in common.CalicoNamespace, which is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove this comment, it's rationale that only benefits the author of this PR and its reviewers

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

func (c *fluentBitComponent) fluentBitConfigMap() *corev1.ConfigMap {
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{Kind: "ConfigMap", APIVersion: "v1"},
ObjectMeta: metav1.ObjectMeta{Name: c.fluentBitConfConfigMapName(), Namespace: LogCollectorNamespace},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would prefer if the renderFluentBitConf was the first function definition after this one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All taken: renderFluentBitConf now leads the file and reads as service+parsers → addInputs → addFilters → addOutputs, and the Linseed output helpers moved into outputs.go with the store outputs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have E2Es for S3, Splunk and Syslog that can serve as regression testing?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not operator E2Es. The calico-private side has the container fv (fluent-bit/fv/container/, design verification step 3) which drops fixture log lines per type and asserts delivery against local S3/Splunk/Syslog mocks using the real fluent-bit binary and the same config shapes the operator renders. On the operator side the golden-config suite added in this PR pins the exact rendered YAML per scenario. There is no e2e against real S3/Splunk/Syslog endpoints today (that was true of fluentd too).

@pasanw pasanw Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need a test suite that has the exact level of coverage provided by test.sh for the fluentd equivalent of this code, especially given that we are performing a migration here. My ideal would be to see that we have each case from test.sh represented as a test in this repo, with the expectation as a raw YAML file that has a comment showing the fluentd equivalent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added golden_test.go + testdata/rendered-configs/: 17 goldens of the exact rendered config, byte-compared (UPDATE_RENDERED_CONFIGS=1 regenerates), each table entry naming its test.sh ancestor, reusing test.sh's endpoint/bucket/log-type values so the goldens read side-by-side with the fluentd .cfg files. The README maps all 20 test.sh cases: the six ES-era cases are n/a (ES output retired with fluentd per EV-6164), -nch cases map to hostScope NonClusterOnly, and the honest divergences are documented — the operator's Audit syslog type always enables both audit kinds (fluentd's env could do kube-only), the "all log types" case is capped at the CRD's legal enum (Audit;DNS;Flows;IDSEvents — the L7/runtime/WAF syslog envs were never operator-exposed), and the dual-scheme splunk case is split into splunk-http/splunk-https since the CRD takes one endpoint.

log.Info("Managed kubernetes EKS found, getting necessary credentials and config")
if instance.Spec.AdditionalSources != nil {
if instance.Spec.AdditionalSources.EksCloudwatchLog != nil {
esClusterConfig, err = utils.GetElasticsearchClusterConfig(ctx, r.client)

@pasanw pasanw Jul 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth touching base with @caseydavenport on the steps taken in this PR to remove direct ES connections - just to make sure you are aligned with what he had in mind

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do — the ES retirement steps here follow the EV-6164 design (ES output dropped with fluentd, Linseed as sole structured-log sink). Casey, the specific surface in this PR: fluentd's ES env plumbing and the ElasticsearchEksLogForwarderUserSecret/cluster-config watches are removed from the LogCollector controller; nothing else touches ES.

Comment thread pkg/render/logcollector/outputs.go Outdated
if s3 == nil {
return
}
// tag → S3 path segment. The segments preserve fluentd's archive layout

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude says:

Bug: fluent-bit's S3 object keys don't match fluentd's, despite a code comment in outputs.go claiming the archive layout is "preserved."

  • fluentd (out-s3-flows.conf): used fluent-plugin-s3's default s3_object_key_format = %{path}%{time_slice}_%{index}… with path "flows" — path and date concatenated, no separator → key flows20260101_0.gz.
  • fluent-bit (outputs.go): s3_key_format = "/flows/%Y%m%d_$INDEX.gz" — a / inserted between flows and the date → key flows/20260101_0.gz.

Impact: the extra / turns flowsYYYYMMDD… into flows/YYYYMMDD…. Any downstream tooling globbing or parsing the old flat prefix silently reads nothing after upgrade — directly contradicting the comment that says the segments preserve the layout so "existing downstream consumers keep reading the same prefixes."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch that the layout differed while the comment claimed parity. After weighing exact-parity against fluent-bit's directory-style keys, we're keeping the directory layout as a deliberate, release-noted change rather than inheriting fluentd's flat concatenation: keys are now <bucketPath>/<type>/%Y%m%d_<uuid>.gz (one directory per log type, non-cluster flows in their own non_cluster_flows/ directory instead of being mixed into flows/). Rationale: full parity was impossible anyway — the _<index> suffix had to become $UUID because out_s3 tracks its index in a store_dir that doesn't survive pod restarts, so a restarted pod would silently overwrite same-day objects (fluent-plugin-s3 avoided this by checking object existence, which out_s3 doesn't) — and given a documented break either way, the directory layout is materially better for the S3 console, lifecycle tooling and Athena/Glue partitioning. Prefix filters (<bucketPath>/flows) keep matching under both layouts; only patterns anchored to the flat form (flows2026*) need a one-time update, which the release note calls out. The code comment now states the break explicitly, and the goldens show the exact keys per scenario.

}

if c.cfg.NonClusterHost != nil && c.cfg.OSType == rmeta.OSTypeLinux {
cfg.Pipeline.Inputs = append(cfg.Pipeline.Inputs, map[string]interface{}{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude says:

Bug: the fluent-bit :9880 HTTP input — which receives non-cluster-host logs relayed by voltron — omits the body-size limit fluentd's HTTP source had, so large POSTs are silently rejected/truncated.

  • fluentd (fluent_sources.conf, port 9880): body_size_limit 100m.
  • fluent-bit (config.go, [INPUT] Name http port 9880): no size set → inherits in_http's much smaller default buffer_max_size (~4M).

Impact: a voltron-relayed non-cluster-host batch larger than ~4M is rejected → those flow/DNS/policy-activity records silently never reach Linseed. Nothing in the CRD or operator surfaces an error.

Fix: set buffer_max_size 100M (and a matching buffer_chunk_size) on the input.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed and fixed — fluentd's :9880 source had body_size_limit 100m; the fluent-bit input now sets buffer_max_size 100M (plus buffer_chunk_size 5M so a full-size relayed batch doesn't grow 512K at a time), with a render test asserting both.

@hjiawei hjiawei force-pushed the fluent-bit-deploy branch from 0c9d6bc to 33be6fd Compare July 10, 2026 14:27
@@ -129,18 +129,27 @@ func add(mgr manager.Manager, c ctrlruntime.Controller) error {
}

for _, secretName := range []string{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this wasn't there before, but you might want to add a daemonset watch to trigger re reconciliation if the daemonset was removed. This is missing in a lot of places currently.

// renderFluentBitConf renders the fluent-bit configuration (in fluent-bit's
// YAML schema) that fluentBitConfigMap ships: the service and parser
// definitions, then the inputs → filters → outputs pipeline.
func (c *fluentBitComponent) renderFluentBitConf() string {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like, and I might be wrong, that this is all in service of just the config and every function that's called in this chain has nothing to do with rendering (which the component represents).

If this is true, consider attaching all the methods to the FluentBitConfiguration, or reversing the relationship so it's all attached to the fluendBitConfig struct and you pass the specific config options from the FluentBitConfiguration that pertain just to fluentd configuration.

This would be in further service of separating fluentd specific configuration (which is complex) from the component rendering.

{Name: "DB_DIR", Value: c.path("/var/log/calico/calico-fluent-bit")},
}, migratorEnv...)
}
initContainers := []corev1.Container{{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm imagining (please correct me if I'm wrong) that this just needs to be done once. If that's true, are we able to detect if we've already done the migration and not attach the init container?

I'm thinking that if we can skip this init container it's one less thing in the way of getting fluentd starting up. Might not be a huge deal in prod, but cutting out time to setup in the CI would be helpful.

hjiawei and others added 8 commits July 10, 2026 09:59
Replace the fluentd DaemonSet with fluent-bit for log collection and
forwarding. The LogCollector controller now renders the calico-fluent-bit
DaemonSet (Linux and Windows) in calico-system, and pkg/render/fluentd.go is
replaced by fluentbit.go.

- Ship fluent-bit logs to Linseed through its built-in http output.
- Rename the FluentdDaemonSet* API types to FluentBitDaemonSet*
  (fluentd_daemonset_types.go -> fluentbit_daemonset_types.go). Preserve the
  deprecated fluentdDaemonSet override field name/json tag as an alias and
  widen its enums to accept both the new calico-fluent-bit* names and the
  legacy fluentd names so existing LogCollector specs still validate;
  translateLegacyFluentdOverrides remaps the legacy names.
- Warn on invalid fluent-bit-filters ConfigMap content (e.g. left in fluentd
  <filter> syntax) instead of silently dropping it.
- Drop the bogus "calico-fluent-bit" entry from the manager cluster-wide
  namespace list; fluent-bit runs in calico-system, which is already listed.
- Regenerate deepcopy, the operator CRD and enterprise versions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiawei Huang <jiawei@tigera.io>
Forward flow, DNS and policy-activity logs from non-cluster hosts through
voltron to the in-cluster calico-fluent-bit http input, and on to Linseed.

- Grant dnslogs (alongside flowlogs and policyactivity) on the
  non-cluster-host ClusterRole so the minted host token passes voltron's
  SubjectAccessReview for the DNS ingestion path instead of 403ing.
- Set VOLTRON_LOG_COLLECTOR_CA_BUNDLE_PATH on the manager so voltron verifies
  the calico-fluent-bit http input's TLS server certificate against the
  trusted CA bundle it already mounts (the config default
  /etc/pki/tls/certs/ca.crt is not mounted, so the handshake otherwise fails).
- Pass NonClusterHost to the Windows fluent-bit configuration so the Linux
  and Windows renders produce the shared allow-calico-fluent-bit NetworkPolicy
  identically. Otherwise, on clusters with Windows nodes, the port-9880
  ingress rule (voltron -> http input) flapped on every reconcile and
  intermittently dropped voltron's access. Adds a controller regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiawei Huang <jiawei@tigera.io>
pkg/render/fluentbit.go had grown to ~1900 lines. Move the fluent-bit /
EKS log-forwarder rendering into a new pkg/render/logcollector package,
split across focused files (logcollector core, config, outputs, daemonset,
rbac, networkpolicy, eks_log_forwarder) plus the moved tests.

A small set of symbols stays in package render (new pkg/render/logcollector.go)
to avoid a render -> render/logcollector import cycle, since Guardian, Manager,
compliance, apiserver, dex and intrusion detection reference them: the
log-collector network-policy identity (FluentBitSourceEntityRule,
EKSLogForwarderEntityRule, LogCollectorNamespace, the fluent-bit node names,
FluentBitInputService), the shared Linseed-token constants, and the
TrustedBundleVolume helper. The logcollector package aliases these. The shared
pod helper setNodeCriticalPod is exported as SetNodeCriticalPod (matching its
sibling SetClusterCriticalPod).

Pure code move; no behavior change. Build, vet, unit tests, format-check and
gen-files all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Jiawei Huang <jiawei@tigera.io>
…t cert coupling

Live validation surfaced that every fluent-bit Prometheus target was down
with scrape timeouts: the calico-system.prometheus egress policy still
allowed only the fluentd-era metrics port 9081 (which stays — it is also
the elasticsearch-metrics port) and was never taught fluent-bit's 2020.
Add the egress rule and update the policy fixtures. Verified end-to-end
on a GCP kubeadm cluster: targets went from 0 up / 6 down to all up.

Since the fluent-bit metrics endpoint is scraped over plain HTTP, the
monitor controller has no use for the fluent-bit certificate: drop it
from Prometheus's trusted bundle and remove the fluent-bit and
EKS-log-forwarder secret watches (the latter was dead even in the
fluentd era — never in the bundle), decoupling the monitor controller
from the LogCollector controller entirely. Linseed's trust of the
fluent-bit client certificate (logstorage secrets controller) is
unchanged — that hop is real mTLS.

Also use the ServiceMonitor name constant in the watch error message
(review feedback) and refresh serviceMonitorFluentBit's godoc, which
still explained a TLS-era rationale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…EKS RBAC

Legacy overrides (review: the container rename is a breaking change):
route the deprecated fluentdDaemonSet field through the shared
containerNameAliases mechanism in pkg/render/common/components instead
of a bespoke translation, mapping the fluentd-era container and
init-container names to their calico-fluent-bit equivalents, and add
render tests for the alias path and for calicoFluentBitDaemonSet
precedence when both fields are set.

EKS forwarder RBAC: the legacy-cleanup list deleted the cluster-scoped
eks-log-forwarder ClusterRoleBinding that the EKS render re-creates
under the same (fluentd-era) name; creates are processed before deletes,
so on EKS clusters every reconcile deleted the forwarder's Linseed RBAC.
The binding is reused in place, so drop it from the cleanup list (and
add the missed fluentd-node-windows Linseed token secret).

Additional-store parity with fluentd (verified against ee_entrypoint.sh,
syslog-config.sh, splunk-config.sh and the out-s3-*.conf templates):
- hostScope gates only cluster *flow* logs for all three stores, and
  non-cluster flows always ship when a store is enabled; the previous
  render dropped every cluster type but NCH flows under NonClusterOnly
  and never shipped NCH flows under All.
- S3: preserve the legacy archive prefixes (audit_tsee, ...), gzip the
  objects to match their .gz suffix (fluent-plugin-s3's default
  store_as), and drop waf, which the entrypoint never archived. NCH
  flows get their own non_cluster_flows/ prefix — out_s3's $INDEX is
  per-output, so sharing flows/ would overwrite objects.
- syslog: drop syslog_severity_preset "info" — the property is an
  integer (atoi("info") = 0 = Emergency) and the default 6 already is
  info — and add syslog_hostname_preset ${NODENAME} as the node-name
  fallback for tags whose records carry no host key, matching fluentd's
  static SYSLOG_HOSTNAME.
- parse audit.* and ids.events event time (time_key) like the fluentd
  sources did, so S3 day partitions and syslog timestamps use event
  time. Rendered configs (default and all-stores) validated with the
  real fluent-bit image via --dry-run.

Windows: pass Tenant/ExternalElastic into the Windows render so the
shared allow-calico-fluent-bit policy cannot flap on multi-tenant
management clusters with Windows nodes, and the Windows Linseed outputs
target the right namespace and tenant header.

Cleanup: remove the ES-era ElasticsearchEksLogForwarderUserSecret and
cluster-config watches (nothing reads them since the ES output was
dropped) and the unused /etc/fluent-bit/certs trusted-bundle mounts.
Add a controller test for the fluent-bit-filters warning path
(SetWarning on unparseable content, cleared once fixed, rendering
continues) and tighten the store-output render assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…oller

Shared component (review): the resources both OS renders previously
duplicated — the allow-calico-fluent-bit NetworkPolicy, GKE
ResourceQuota, S3/Splunk credential copies, managed-cluster
tigera-linseed Service/RoleBinding, PacketCapture RBAC and the legacy
cleanup — move into a FluentBitShared component rendered exactly once
per reconcile, removing the duplicate-render contention class. The
Windows configuration no longer carries NonClusterHost at all.

Legacy cleanup: rewritten to the namespace-cascade pattern used for the
guardian/apiserver/policy-recommendation migrations — delete the
tigera-fluentd Namespace plus the cluster-scoped ClusterRole/Bindings
and the operator-namespace copy of tigera-fluentd-prometheus-tls.
Never-in-production entries (the allow-tigera-prefixed policy name and
the calico-system copy of the filters ConfigMap) are dropped.

DaemonSet: startup/liveness probes move to /api/v1/uptime so sustained
delivery errors cannot restart pods and drop in-memory buffers
(readiness keeps the error-aware /api/v1/health);
terminationGracePeriodSeconds 0 → 30 (grace 0 assumed the pos file made
buffers durable, which was never true); pos-migrator init container
unified across OS variants.

Outputs/config: S3 keys switch to directory-style
<bucketPath>/<type>/%Y%m%d_$UUID.gz — a deliberate, release-noted
layout change from fluentd's flat concatenation (full parity was
impossible anyway: out_s3's $INDEX is not restart-safe, so the suffix
had to become a UUID) — with non-cluster flows in their own directory;
the :9880 http input gains buffer_max_size 100M / buffer_chunk_size 5M
(fluentd's body_size_limit parity — the ~4M default silently rejected
larger relayed non-cluster-host batches); splunk drops l7, which
deployed fluentd never enabled; renderFluentBitConf now reads as
inputs → filters → outputs with the Linseed helpers moved next to the
store outputs; buffer-durability comments corrected (fluentd buffered
in memory only).

Controller: the rendered fluent-bit ConfigMaps are watched so tampering
triggers reconciliation, and syslog endpoints are validated to tcp/udp
schemes (fluent-bit's syslog mode accepts nothing else; TLS comes from
the encryption field).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review ask: represent each case from fluentd's config test harness
(calico-private fluentd/test/test.sh) in this repo, with the
expectation as a raw file mapped to its fluentd equivalent.

TestRenderedConfigGoldens pins the full rendered fluent-bit config for
17 scenarios byte-for-byte under testdata/rendered-configs/, each table
entry naming its test.sh ancestor and reusing test.sh's endpoint,
bucket and log-type values so a golden reads side-by-side with its
fluentd .cfg counterpart. The README maps all 20 test.sh cases: the six
ES-era cases are n/a (the Elasticsearch output was retired with fluentd
per EV-6164), -nch cases map to hostScope NonClusterOnly, and the
divergences that cannot be mirrored are documented (the Audit syslog
type always enables both audit kinds, the CRD never exposed the
L7/runtime/WAF syslog toggles, and the dual-scheme splunk case splits
into splunk-http/splunk-https). UPDATE_RENDERED_CONFIGS=1 regenerates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the never-in-production calico-fluent-bit-metrics PodMonitor
from the monitor toDelete list (the pre-v1.25 PodMonitor was named
fluentd-metrics; the fluent-bit name never shipped) and simplify the
ServiceMonitor comment that duplicated the function godoc.

Drop the fluent-bit and eks-log-forwarder ingress rules from the
es-gateway policy: both components ship only to Linseed in every
topology (managed clusters use the tigera-linseed ExternalService via
Guardian, never tigera-secure-es-gateway-http), so the rules were
relics of fluentd's direct-Elasticsearch path. The equivalent source
rules on the Linseed policy are the live path and remain.

Remove an author-focused rationale comment from the tiers controller.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@hjiawei hjiawei force-pushed the fluent-bit-deploy branch from 33be6fd to 19b7c85 Compare July 10, 2026 16:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-pr-required enterprise Feature applies to enterprise only kind/enhancement New feature or request release-note-required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants