Files
Aleksei Sviridkin 2055341ba9 fix(opensearch-operator): raise vm.max_map_count on every node from a DaemonSet (#4152)
## What this PR does

OpenSearch will not start below `vm.max_map_count=262144` when the store
may use mmap, and nothing in the platform sets it. The Talos profiles
declare no sysctls and no documented node-preparation path sets one, so
nodes sit at the kernel default of 65530. The app chart asks the
operator to cover that with `spec.general.setVMMaxMapCount`, which
appends a privileged init container to every OpenSearch pod. Tenant
namespaces carry no PodSecurity label of their own, so they take the
cluster default, which Talos ships as baseline through the apiserver's
`PodSecurityConfiguration`. That default refuses the container and the
StatefulSet never produces a pod. On vanilla kubeadm, kind or k3s the
default is `privileged` and the same release comes up, which is why this
only bites on a Talos install.

The value cannot come from the pod at all. The kernel does not namespace
`vm.*`, so kubelet rejects it in `securityContext.sysctls`, and refuses
to start if it appears in `--allowed-unsafe-sysctls`. That leaves node
configuration or a privileged pod, and a privileged pod belongs in a
system namespace rather than in every tenant.

This adds a DaemonSet to the `opensearch-operator` package that sets the
value once per node from a privileged init container, and marks the
component privileged so the platform labels `cozy-opensearch-operator`
with `enforce=privileged`. The init container writes to `/proc/sys` and
nothing else, with no hostPath. The pod restarts with the node and
reapplies the value, so there is nothing to persist.

It raises the limit to a floor rather than assigning it, which matters
more than it looks. `vm.max_map_count` is node-global, and a stock
Ubuntu 24.04 node already reads 1048576 from a `sysctl.d` drop-in. An
unconditional write would drop that to 262144 for every workload on the
node, on every boot, and undo whatever the operator set it for. So the
init container reads the current value first and writes only when it is
below the threshold.

The idle main container runs its sleep under a shell that traps SIGTERM,
so a pod exits on its own when the DaemonSet rolls or a node drains,
instead of ignoring the signal and being killed after the full grace
period.

This does not fix OpenSearch on its own. The app chart still requests
the operator's init container through `setVMMaxMapCount: true`, so a
release stays blocked until the flip to `false` lands with #2682, after
this merges. The DaemonSet has to be in place first: flipping it without
one would trade the admission failure for a failed bootstrap check.
Carrying the sysctl in the node contract instead, across the Talos
profiles and the downstream node-prep paths, is tracked in #4144; that
is what would let this DaemonSet be retired.

Two things change on upgrade, and neither is visible from the diff. One
small DaemonSet pod appears per node in `cozy-opensearch-operator`, and
that namespace gains `pod-security.kubernetes.io/enforce=privileged`.
That label has no finer granularity than the namespace, so the operator
Deployment sitting there leaves the cluster default too, not just the
DaemonSet. Keeping the manager at baseline would mean a namespace of its
own and another component entry, which did not look worth it, but it is
a real cost rather than an accident. The operator is in the `paas`
bundle's always-on list, so this lands on every node of every paas
installation and raises a node-global kernel tunable there, whether or
not anyone ever creates an OpenSearchCluster. That is the price of a
node-level sysctl having no cheaper carrier, and it is worth an explicit
decision rather than being noticed later.

The second is `cozypkg add`, which gates privileged components behind a
confirmation or `--allow-privileged`. The gate runs over the resolved
dependency closure, and `opensearch-application` dependsOn the operator,
so installing the app that way now asks one more question. This is not a
new break for scripts: `add` already prompts for a variant on every
package it resolves and fails on EOF, so it was never usable
non-interactively in the first place. Platform-bundle installs go
through the reconciler and are unaffected.

### Screenshots

Not a UI change.

### Downstream repositories

Walked the trigger map in `docs/agents/contributing.md` against the
diff. It adds a template, a test and a values key under
`packages/system/opensearch-operator/`, plus one field in
`packages/core/platform/sources/opensearch-operator.yaml`. No new app
package, no app schema or version enum, no `ApplicationDefinition`, no
installer value, no namespace rename, no variant or bundle, no `hack/`
path or make target, and no node prerequisite in
`hack/e2e-prepare-cluster.bats`. Nothing in the map matches.

- [x] 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

```release-note
fix(opensearch-operator): set `vm.max_map_count` on every node from a DaemonSet in the operator's namespace, so OpenSearch no longer depends on a privileged init container that PodSecurity baseline refuses in a tenant namespace. Upgrading adds one small DaemonSet pod per node and labels `cozy-opensearch-operator` with `pod-security.kubernetes.io/enforce=privileged`; installing the package with `cozypkg add` now requires confirmation or `--allow-privileged`. Existing OpenSearch releases are unchanged until a follow-up stops the app chart requesting the operator's own init container.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **Bug Fixes**
- OpenSearch Operator installation now runs with the required privileged
configuration.
- OpenSearch deployments automatically ensure nodes meet the required
`vm.max_map_count` setting, improving startup reliability.
  - Existing higher kernel values are preserved.
- Added support for configuring the sysctl image, including use with
air-gapped registries.

- **Tests**
- Added coverage to verify privileged installation, node configuration,
security settings, scheduling behavior, and image customization.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-08 19:45:14 +04:00
..