Repository navigation
Grant executor capabilities in upgrade-database.yml; fall back when the eBPF counter load fails - #427
Merged
Conversation
upgrade-database.yml activated and started the candidate executor without the file capabilities the executor role grants, so until a full deploy followed the executor ran without CAP_BPF, CAP_PERFMON, CAP_NET_ADMIN and CAP_NET_RAW. Move the getcap/setcap logic into tasks/executor-capabilities.yml and import it from the executor role, rollout-executors.yml and upgrade-database.yml so the three cannot drift. upgrade-database.yml grants the set to the staged candidate before stopping the service; a refusal stops there with the service and database untouched, as in the rollout. The role keeps warning instead of failing, and still restarts the executor when the set changes. The rollout now also skips setcap when the binary already carries the set, and grants it under executor_ambient_caps as the role and verify.yml already expected. test_rollout.py runs upgrade-database.yml against the fixture hosts and checks that setcap precedes stop/upgrade/start, is skipped when the set is present or BPF is disabled, and that a refusal stops before the service is touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In packet_counter = "auto" mode a load failure other than EPERM, EACCES or EINVAL was tagged ErrCleanupUnconfirmed, so the executor exited with "packet counter cleanup unconfirmed" and systemd restarted it in a loop. Without CAP_BPF, cilium/ebpf can report the map create as "prealloc maps not supported" (its refused feature probe), which carries no errno. A load attaches nothing: attach runs only after a successful load, and loading creates no TCX link or pin. Whatever descriptors cilium/ebpf may not have released count no packet and close with the process, so every load failure is now a clean rollback and the factory falls back. A failed attach whose release fails still refuses to fall back. When the process lacks CAP_BPF or CAP_NET_ADMIN (and CAP_SYS_ADMIN), the load error now names the missing capabilities and matches os.ErrPermission, so the fallback reason is not_permitted rather than a MEMLOCK or prealloc hint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
deploy/ansible/upgrade-database.ymlactivates the candidate executor and starts it, but never grants it the file capabilities that the executor role sets (executor_capabilities). Two production executors then failed to create their eBPF maps. Inpacket_counter = "auto"mode they exited withinitialize packet counter: failed to initialize packet count: packet counter cleanup unconfirmedinstead of falling back, and systemd restarted them about 9,000 times.Cause
getcap/setcaplogic existed in two copies, in the executor role and inrollout-executors.yml.upgrade-database.ymlhad no copy. The two copies had also drifted: the rollout always ransetcapand skipped it underexecutor_ambient_caps, while the role compared againstgetcapfirst and ignoredexecutor_ambient_caps.newBPFCountlet only EPERM, EACCES and EINVAL load failures fall back. Every other load failure was taggedErrCleanupUnconfirmed, which makes the factory stop the executor. Without CAP_BPF, cilium/ebpf on kernel 7.0 reports the map create asprealloc maps not supported (requires >= v4.6). That message comes from cilium's own feature probe, which was also refused, and it carries no errno. So the load failure became fatal. Yet a load attaches nothing: attach runs only after a successful load.Fix
Deployment (
deploy/ansible)tasks/executor-capabilities.ymlholds the one copy of the logic. It runsgetcap(also in check mode), then runssetcapfor exactlyexecutor_capabilitiesonly when the set differs. It does nothing whenexecutor_enable_bpfis false.setcap, and still notifiesRestart executorwhen the set changes.rollout-executors.yml: still fails before the drain.upgrade-database.yml: new. It grants the set to the staged candidate before the service is stopped, and a refusal fails the host while the service and the database are still untouched.executor_ambient_caps=truethe rollout now grants the file capabilities. Before this PR it skipped them. The role andverify.ymlalready required them in that case, so the rollout was the one that had drifted.deploy/README.mdand the upgrade-database header comment are updated.Executor (
internal/executor/ratelimit/ebpf)ErrCleanupFailedand stops the executor.setcap. It matchesos.ErrPermission, so the reported fallback reason isnot_permitted. The new error replaces the old text at the front, for exampleexecutor lacks CAP_BPF, CAP_NET_ADMIN (grant the executor binary executor_capabilities with setcap); loader reported: ... MEMLOCK may be too low ....ErrCleanupUnconfirmednow says that a load failure does not carry it.Tests
deploy/test/provisioner-check.shpasses in full: render checks, then 10/10 tests intest_rollout.py. New tests:upgrade-database.ymlrunssetcapwith the exact set on the staged candidate, before stop, upgrade and start, on each host.setcapwhengetcapalready reports the set (in a different order).executor_enable_bpf=false.setcapfails before the service is stopped, leaves the database unchanged, and leaves the second host untouched.notifyonimport_tasksreaches the role's handler.go build/go vetfor./internal/... ./cmd/...pass on darwin and withGOOS=linux.prealloc maps not supportedshape, falls back without attaching;ErrCleanupFailedandErrCleanupUnconfirmed;ratelimit,ebpfandcleanuptests pass on Linux in a container.executor lacks CAP_BPF, CAP_NET_ADMIN .... In a privileged one it loads.Fixes #414
🤖 Generated with Claude Code