Files
cozystack/internal
Aleksei Sviridkin 018b49929f feat(monitoring): auto-route tenant logs using namespace labels (#2195)
<!-- Thank you for making a contribution! Here are some tips for you:
- Use Conventional Commits for the PR title: `type(scope): description`
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Scopes are not an exhaustive list — pick the most specific scope for
the change and extend the list when a genuinely new area appears.
Examples:
- System components: dashboard, platform, operator, cilium, kube-ovn,
linstor, fluxcd, cluster-api
- Managed apps: postgres, mariadb, redis, kafka, clickhouse,
virtual-machine, kubernetes
- Development and maintenance: api, hack, tests, ci, docs, maintenance
- Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or
add a `BREAKING CHANGE:` footer
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `kind/backport` if it's a bugfix that needs to be
backported to a previous version.
-->

## What this PR does

A tenant that runs its own monitoring stack gets a VictoriaLogs
instance, but the node fluent-bit in `cozy-monitoring` sends every
container log to `global.target` (`tenant-root`), so that instance stays
empty. This PR routes each namespace's container logs to the
VictoriaLogs named by its `namespace.cozystack.io/monitoring` label, the
label the tenant chart already sets and the per-tenant vmagent already
selects on.

**Chart (`packages/system/monitoring-agents`).**
`templates/_tenant-log-routes.tpl` looks up the namespaces and keeps
those whose label is set and names a tenant other than `global.target`.
For those, fluent-bit gets one `rewrite_tag` filter with a `Rule` per
target (a single emitter however many tenants are routed), one `http`
output per target to `vlinsert-generic.<target>.svc:9481`, and a
per-target `modify Set tenant <target>`, because the re-tagged record
re-enters the filter chain where the `Match *` `Add tenant` cannot
overwrite the key. Unlabelled namespaces, empty labels and labels equal
to `global.target` stay on the default outputs.

**Controller (`internal/controller/tenantlogrouting`).** `lookup`
results are not part of the digest helm-controller compares, so the
chart alone would not pick up a tenant that turns monitoring on, or drop
one that is removed, until an unrelated upgrade. The new reconciler in
cozystack-controller digests the monitoring label across all namespaces
and, when the digest moves, sets `reconcile.fluxcd.io/requestedAt` and
`reconcile.fluxcd.io/forceAt` on `cozy-monitoring/monitoring-agents`,
which forces an upgrade and re-runs the lookup. It digests every
labelled namespace rather than repeating the chart's selection, so it
cannot drift from the chart; the cost is an occasional upgrade that
renders the same config. The digest it acted on is kept in its own
annotation, so a manual `flux reconcile` does not make it force another
upgrade. The existing ClusterRole already grants the verbs it needs.

**Behaviour change for the platform operator.** Routed records are
re-tagged with `keep false`: once a tenant runs its own monitoring, its
namespaces' container logs go to that tenant's VictoriaLogs and no
longer to the root one. Events and audit logs are not routed and stay on
the root store.

**Why not a per-tenant collector.** An earlier comment on this PR
proposed a fluent-bit per tenant, following the per-tenant vmagent and
the OTLP collector from #3763. That collector receives OTLP over the
network; a log collector has to tail `/var/log` through `hostPath`,
which the PodSecurity level of tenant namespaces rejects, and running it
in a privileged system namespace instead would mean one DaemonSet per
tenant tailing every node's logs. The routing therefore stays in the one
node DaemonSet, with the reconciler closing the reconcile gap.

Tests: `tests/fluent-bit_tenant_log_routing_test.yaml` mocks the
namespaces with `kubernetesProvider` and pins which get a rule and an
output, which are skipped (unlabelled, empty label, label equal to
`global.target`), and that a routed record carries the target tenant.
Three of its cases fail against the previous revision of this branch.
`tests/fluent-bit_tenant_log_routing_offline_test.yaml` pins that an
offline render routes nothing.
`internal/controller/tenantlogrouting/reconciler_test.go` covers first
sight, no-op while labels are unchanged (including after a manual
reconcile), a tenant turning monitoring on, a routed namespace being
deleted, and a missing release.

Refs #2194

### Screenshots

<!-- REQUIRED for UI changes: attach screenshots or screen recordings
demonstrating
the visual impact of your changes. PRs with UI changes without
screenshots will not be merged. -->

### Downstream repositories

<!--
Cozystack is upstream for repositories that are NOT kept in sync with it
automatically, and nothing in CI compares the two sides, so a change
here can break them silently.

Which change forces what, and which file to touch, is the trigger map in
docs/agents/contributing.md under "Downstream Repositories". Walk it
before you tick anything.

IF YOU ARE AN AI AGENT OR AN LLM, THIS IS PART OF THE TASK, NOT A FORM
TO FILL IN:

1. Walk the trigger map against the actual diff of this PR, file by
file. Do not decide from the PR title or from your memory of what you
changed.
2. For every repository you tick, open the follow-up PR there and link
it on the same line. A ticked box with no link claims work that does not
exist, which is worse than an empty box. Search that repository for an
open PR or issue covering it first, and link that instead of filing a
duplicate.
3. When the follow-up is out of scope, or needs a decision that is not
yours to make, open an issue in that repository instead, link that, and
say so in the PR body. Do not push a speculative PR into another
repository just to fill a line here.
4. Do not tick "No downstream repository is affected" to make the
checklist go away. If you are unsure, leave every box empty and say so
in the PR body, so a human decides.
-->

No entry of the trigger map matches the diff: it touches
`packages/system/monitoring-agents` (values, a template helper, tests),
a new reconciler in `internal/controller`, its registration in
`cmd/cozystack-controller`, and a comment in the cozystack-controller
ClusterRole. The one open question is `cozystack/website`:
`content/en/docs/next/operations/services/monitoring/logs.md` describes
log collection in general terms and does not say where a tenant's
container logs land, so the behaviour change above could deserve a
sentence there. I have left every box empty for a maintainer to decide.

- [ ] No downstream repository is affected by this change
- [ ] [cozystack/website](https://github.com/cozystack/website) -
follow-up:
- [ ]
[cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack)
- follow-up:
- [ ]
[cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack)
- follow-up:
- [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up:
- [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up:
- [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) -
follow-up:
- [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) -
follow-up:
- [ ]
[cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server)
- follow-up:
- [ ]
[cozystack/external-apps-example](https://github.com/cozystack/external-apps-example)
- follow-up:
- [ ] [cozystack/examples](https://github.com/cozystack/examples) -
follow-up:
- [ ] [cozystack/community](https://github.com/cozystack/community) -
follow-up:

### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same `type(scope):` prefix as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
feat(monitoring): container logs from a tenant that runs its own monitoring stack now go to that tenant's VictoriaLogs instead of the root one. The node fluent-bit routes them by the namespace.cozystack.io/monitoring label, and cozystack-controller re-renders the node agents when that label changes on any namespace.
```
2026-09-29 21:18:52 +03:00
..