Skip to content
Draft
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
79d020c
Add design spec for conditional init() execution in busybox binary
janisz Apr 13, 2026
ce0bf8d
Add implementation plan for conditional init() execution
janisz Apr 13, 2026
99f16b5
feat: create central/app package structure
janisz Apr 13, 2026
391316f
docs: analyze sensor → central import chains
janisz Apr 13, 2026
6834046
feat: establish app/ structure for config-controller
janisz Apr 13, 2026
29b615f
fix: move admission-control init() to explicit initialization
janisz Apr 13, 2026
2cf4237
refactor: add GraphQL loader init structure (stub)
janisz Apr 13, 2026
2af5b79
refactor: add compliance init structure (stub)
janisz Apr 13, 2026
eb3f1e0
refactor: move Central metrics from init() to explicit registration
janisz Apr 13, 2026
bdfdc77
refactor: move sensor metrics init() to explicit initialization
janisz Apr 13, 2026
2287e20
docs: add verification report and architecture guide
janisz Apr 13, 2026
0449ea6
docs: Phase 5 low-hanging fruit analysis and migration plan
janisz Apr 13, 2026
8b5a49f
docs: add busybox-scoped Phase 5 recommendations
janisz Apr 13, 2026
eca4ef0
docs: add heap profile component labeling fix
janisz Apr 13, 2026
d3de83e
feat: add component labeling for heap/CPU profiles
janisz Apr 13, 2026
987c76a
docs: update heap profile labeling doc with implementation details
janisz Apr 13, 2026
82a6968
refactor: minimize metrics init diff by keeping logic in metrics pack…
janisz Apr 13, 2026
c78f077
chore: remove documentation files
janisz Apr 13, 2026
93f7842
refactor: remove app/init.go files, call metrics.Init directly from a…
janisz Apr 13, 2026
db6b9d5
fix: update metric Init() comments to reference app.go
janisz Apr 13, 2026
f1a6ada
refactor: migrate GraphQL loaders and compliance checks to explicit I…
janisz Apr 13, 2026
6744213
refactor: rename init() to register*() in kubernetes compliance checks
janisz Apr 13, 2026
9b30c56
refactor: rename init() to Register*() in hipaa_164 compliance checks
janisz Apr 13, 2026
f24eb46
refactor: rename init() to Register*() in nist80053 compliance checks
janisz Apr 13, 2026
668c7ef
refactor: rename init() to Register*() in nist800-190 compliance checks
janisz Apr 13, 2026
0a704c0
refactor: rename init() to Register*() in pcidss32 compliance checks
janisz Apr 13, 2026
b84cb73
refactor: replace blank imports with explicit Init() calls in complia…
janisz Apr 13, 2026
3b1086c
refactor: rename init() to Register*() in central hipaa_164 complianc…
janisz Apr 13, 2026
530b499
refactor: rename init() to Register*() in central nist800-190 complia…
janisz Apr 13, 2026
f487fd3
refactor: rename init() to Register*() in central nist80053 complianc…
janisz Apr 13, 2026
4e8afdd
refactor: rename init() to Register*() in central pcidss32 compliance…
janisz Apr 13, 2026
f8083d4
refactor: rename init() to Init() in central remote compliance checks
janisz Apr 13, 2026
4c0dbf7
refactor: replace blank imports with explicit Init() in central compl…
janisz Apr 13, 2026
998006c
refactor: rename init() to Register*() in all notifier factories
janisz Apr 13, 2026
3ab5d12
refactor: rename init() to Register*() in compliance standards metadata
janisz Apr 13, 2026
5dfcdc9
refactor: rename init() to Register*() in external backup plugins
janisz Apr 13, 2026
26ed417
refactor: migrate init() to explicit Init() pattern and centralize pr…
janisz Apr 14, 2026
e9f029f
fix: break import cycle in sensor telemetry gatherers
janisz Apr 14, 2026
4eb3d12
fix: resolve golangci-lint failures from init() migration
janisz Apr 14, 2026
51425e9
fix: expand gochecknoinits exclusion to cover all legacy directories
janisz Apr 14, 2026
441a472
refactor: migrate init() to explicit Init() pattern across all compon…
janisz Apr 14, 2026
d62a670
refactor: migrate init() to explicit Init() in roxctl, sensor, tools,…
janisz Apr 14, 2026
a7c0386
style: fix gofmt formatting in volume converter files
janisz Apr 14, 2026
0d397c9
config: add pkg/images/enricher/metadata.go to gochecknoinits exclusion
janisz Apr 14, 2026
0cb5d2a
refactor: migrate migrator init() to explicit Register() pattern
janisz Apr 14, 2026
6fceb99
refactor: migrate remaining 9 pkg/ init() functions to explicit Init()
janisz Apr 14, 2026
dd31084
refactor: unexport centralRun - only used within main package
janisz Apr 14, 2026
aeee0e4
fix: apply critical fixes from split PRs to main branch
janisz Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions central/app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package app

import (
"github.com/stackrox/rox/pkg/logging"
"github.com/stackrox/rox/pkg/memlimit"
"github.com/stackrox/rox/pkg/premain"
"github.com/stackrox/rox/pkg/profiling"
)

var (
log = logging.LoggerForModule()
)

// Run is the main entry point for the central application.
// Performs early initialization and component-specific setup before
// main.CentralRun() starts the actual central service logic.
func Run() {
profiling.SetComponentLabel()
memlimit.SetMemoryLimit()
premain.StartMain()

initMetrics()
initCompliance()
initGraphQL()
}
29 changes: 29 additions & 0 deletions central/app/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package app

import (
"github.com/stackrox/rox/central/metrics"
)

func initMetrics() {
metrics.Init()
}

// initCompliance registers all compliance checks.
func initCompliance() {
// Import side-effect: pkg/compliance/checks registers all standard checks via init()
// We consolidate that registration here by calling the registration function explicitly
// This is handled by importing central/compliance/checks/remote which calls MustRegisterChecks

// The actual registration is done via the package import
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
// Future work: refactor compliance/checks to use explicit registration
}

// initGraphQL registers all GraphQL type loaders.
func initGraphQL() {
// GraphQL loaders registration
// Each loader registers itself via RegisterTypeFactory in their init() functions

// Similar to compliance checks, this requires refactoring the loader registration
// to be explicit rather than init()-based
// Stub for now - full migration in separate PR
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
}
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.

⚠️ Potential issue | 🟠 Major

These helpers do not remove the busybox init cost yet.

Both functions are stubs, and the comments explicitly say the real registration still happens through package-import side effects. In the consolidated binary, those init() paths still run before dispatch, so non-central components will continue paying the compliance/GraphQL initialization cost this PR is trying to isolate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@central/app/init.go` around lines 47 - 65, The stub initCompliance and
initGraphQL still rely on package init() side-effects so the unwanted
initialization cost remains; replace the implicit side-effect imports by calling
explicit registration functions during central startup: in initCompliance call
the concrete registration routine (e.g.,
compliance/checks/remote.MustRegisterChecks or equivalent exported
RegisterAllChecks function) instead of relying on package init(), and in
initGraphQL invoke the GraphQL loader registration entrypoint (the
package-exported function that performs RegisterTypeFactory for all loaders)
rather than importing packages for side effects; remove any blank (_) imports
that trigger init() and ensure initCompliance and initGraphQL perform the
explicit calls in the central init path.

10 changes: 7 additions & 3 deletions central/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ import (
pkgVersion "github.com/stackrox/rox/pkg/version"

// BusyBox-style consolidation - import app packages
app "github.com/stackrox/rox/central/app"
complianceapp "github.com/stackrox/rox/compliance/cmd/compliance/app"
roxagentapp "github.com/stackrox/rox/compliance/virtualmachines/roxagent/app"
configcontrollerapp "github.com/stackrox/rox/config-controller/app"
Expand Down Expand Up @@ -281,7 +282,9 @@ func runSafeMode() {
log.Info("Central terminated")
}

func centralRun() {
// CentralRun is the main central application logic.
// Exported temporarily to allow central/app package to call it.
func CentralRun() {
defer utils.IgnoreError(log.InnerLogger.Sync)

premain.StartMain()
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.

issue (bug_risk): Central mode now runs premain and initialization twice and default mode misses app-level init

In the central case, both app.Run() and CentralRun() are called. Since app.Run() already invokes memlimit.SetMemoryLimit() and premain.StartMain(), and CentralRun() also calls premain.StartMain(), premain now runs twice. Meanwhile, the default branch calls only CentralRun() and skips app.Run(), so app-level init (metrics registration, compliance/GraphQL setup) is missed.

Please refactor so there is a single, consistent initialization path—for example, by having all memlimit/premain logic live in app.Run() and always invoking it before CentralRun(), or by making CentralRun() the single entry point that performs initialization and is called from app.Run().

Expand Down Expand Up @@ -1086,7 +1089,8 @@ func main() {

switch binaryName {
case "central":
centralRun()
app.Run()
CentralRun()
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.

issue (bug_risk): Avoid calling premain.StartMain (and other early-init) twice for the central binary

For the "central" case, both app.Run() and CentralRun() are now called. Since app.Run() calls premain.StartMain() and CentralRun() still invokes it via the old centralRun logic, premain runs twice, risking double-initialization, extra goroutines, or panics. Centralize premain.StartMain() (and other early-init like memlimit/metrics) in a single path—e.g., only in app.Run() and remove it from CentralRun(), or ensure only one of these is called from main().

case "migrator":
migratorapp.Run()
case "compliance":
Expand All @@ -1106,6 +1110,6 @@ func main() {
default:
// Default to central if called with unknown name
log.Warnf("Unknown binary name %q, defaulting to central mode", binaryName)
centralRun()
CentralRun()
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.

⚠️ Potential issue | 🟡 Minor

Default-to-central path skips the new central init flow.

The "central" branch runs app.Run() before CentralRun(), but the fallback only calls CentralRun(). If this binary is invoked under any unexpected name, it will start central without the new metric/compliance/GraphQL initialization even though the log says it is defaulting to central mode.

🔧 Proposed fix
 	default:
 		// Default to central if called with unknown name
 		log.Warnf("Unknown binary name %q, defaulting to central mode", binaryName)
+		app.Run()
 		CentralRun()
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Default to central if called with unknown name
log.Warnf("Unknown binary name %q, defaulting to central mode", binaryName)
centralRun()
CentralRun()
// Default to central if called with unknown name
log.Warnf("Unknown binary name %q, defaulting to central mode", binaryName)
app.Run()
CentralRun()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@central/main.go` around lines 1111 - 1113, The fallback path that handles
unknown binary names logs defaulting to central but skips the new initialization
flow by calling CentralRun() directly; update the fallback to invoke app.Run()
(the same initialization performed in the "central" branch) before calling
CentralRun() so metric/compliance/GraphQL startup is performed; locate the
log.Warnf("Unknown binary name %q, defaulting to central mode", binaryName)
branch and insert or call app.Run() prior to CentralRun().

}
}
4 changes: 3 additions & 1 deletion central/metrics/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"github.com/prometheus/client_golang/prometheus"
)

func init() {
// Init registers all Central prometheus metrics.
// Called explicitly from central/app/init.go instead of package init().
func Init() {
// general

prometheus.MustRegister(
Expand Down
2 changes: 2 additions & 0 deletions compliance/cmd/compliance/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stackrox/rox/pkg/env"
"github.com/stackrox/rox/pkg/logging"
"github.com/stackrox/rox/pkg/memlimit"
"github.com/stackrox/rox/pkg/profiling"
"github.com/stackrox/rox/pkg/retry/handler"
)

Expand All @@ -20,6 +21,7 @@ var (

// Run is the main entry point for the compliance application.
func Run() {
profiling.SetComponentLabel()
memlimit.SetMemoryLimit()

if err := continuousprofiling.SetupClient(continuousprofiling.DefaultConfig()); err != nil {
Expand Down
2 changes: 2 additions & 0 deletions compliance/virtualmachines/roxagent/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import (

"github.com/stackrox/rox/compliance/virtualmachines/roxagent/cmd"
"github.com/stackrox/rox/pkg/logging"
"github.com/stackrox/rox/pkg/profiling"
)

var log = logging.LoggerForModule()

// Run is the main entry point for the roxagent application.
func Run() {
profiling.SetComponentLabel()
// Create a context that is cancellable on the usual command line signals. Double
// signal forcefully exits.
ctx, cancel := context.WithCancel(context.Background())
Expand Down
2 changes: 2 additions & 0 deletions config-controller/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/stackrox/rox/config-controller/pkg/client"
"github.com/stackrox/rox/pkg/env"
"github.com/stackrox/rox/pkg/logging"
"github.com/stackrox/rox/pkg/profiling"
"github.com/stackrox/rox/pkg/tlsprofile"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
Expand Down Expand Up @@ -62,6 +63,7 @@ func init() {

// Run is the main entry point for the config-controller application.
func Run() {
profiling.SetComponentLabel()
var metricsAddr string
var probeAddr string
var secureMetrics bool
Expand Down
7 changes: 7 additions & 0 deletions config-controller/app/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package app

// initMetrics registers config-controller-specific prometheus metrics.
// Currently config-controller has minimal metrics, so this is a stub for future use.
func initMetrics() {
// No metrics to register yet - config-controller has very light init overhead
}
68 changes: 68 additions & 0 deletions docs/architecture/conditional-init.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Conditional Initialization in Busybox Binary

## Overview

StackRox uses a busybox-style consolidated binary where multiple components (central, sensor, admission-control, config-controller, etc.) are built into a single binary and dispatched via os.Args[0].

## Problem

Go's package init() functions run when packages are imported, regardless of which component will actually execute. This caused all 535 init() functions to run for every component, leading to:

- Memory overhead (~166 MB for sensor, ~64 MB for central)
- OOMKills under race detector (config-controller: 7 restarts, admission-control: 6-7 restarts)

## Solution

Move initialization from package-level init() to explicit component-specific functions called from app.Run().

### Architecture

```
central/main.go (dispatcher)
↓ os.Args[0] check
component/app/app.go
↓ Run()
component/app/init.go
↓ initMetrics(), initCompliance(), etc.
```

### Migration Status

**Completed:**
- ✅ central/metrics (37 metrics)
- ✅ sensor/common/metrics (42 metrics)
- ✅ sensor/admission-control (6 metrics)

**Stubs (future work):**
- ⏳ compliance checks (109 files)
- ⏳ GraphQL loaders (15 files)

**Not migrated:**
- Shared infrastructure (logging, feature flags, env settings)
- Simple/negligible init() functions

## Usage

When adding new component-specific initialization:

1. Add function to `component/app/init.go`
2. Call from `component/app/app.go Run()` function
3. Avoid package-level init() for component-specific code

## Testing

Verify no cross-component init() execution:

```bash
# Check import chains
go list -f '{{.Deps}}' ./sensor/kubernetes | grep "central/"

# Should return no central/* packages (pkg/* is okay)
```

## References

- Design spec: docs/superpowers/specs/2026-04-13-conditional-init-design.md
- Implementation plan: docs/superpowers/plans/2026-04-13-conditional-init.md
- Verification report: docs/init-migration-verification.md
- ROX-33958: BusyBox binary consolidation OOMKill fixes
129 changes: 129 additions & 0 deletions docs/compliance-check-init-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Compliance Check Init Migration

## Current State
- 109 compliance check files have init() functions
- Each calls framework.MustRegisterChecks() individually
- Registration happens via package init when imported

## Migration Strategy
Phase 3.2 establishes initCompliance() stub in central/app/init.go

Future work (separate PR):
1. Refactor pkg/compliance/checks to export registration functions
2. Call those functions from central/app/init.go initCompliance()
3. Remove init() from all 109 check files

## Files affected:

### central/compliance/checks/
- remote/all.go
- nist80053/check_cm_11/check.go
- nist80053/check_cm_7/check.go
- nist80053/check_sa_10/check.go
- nist80053/check_ra_3/check.go
- nist80053/check_ac_14/check.go
- nist80053/check_cm_3/check.go
- nist80053/check_cm_2/check.go
- nist80053/check_si_4/check.go
- nist80053/check_ir_4_5/check.go
- nist80053/check_cm_8/check.go
- nist80053/check_sc_6/check.go
- nist80053/check_ir_5/check.go
- nist80053/check_cm_6/check.go
- nist80053/check_ir_6_1/check.go
- nist80053/check_ca_9/check.go
- nist80053/check_cm_5/check.go
- nist80053/check_sc_7/check.go
- nist80053/check_ra_5/check.go
- nist80053/check_si_3_8/check.go
- nist80053/check_si_2_2/check.go
- pcidss32/check811/check.go
- pcidss32/check12/check.go
- pcidss32/check24/check.go
- pcidss32/check71/check.go
- pcidss32/check62/check.go
- pcidss32/check85/check.go
- pcidss32/check22/check.go
- pcidss32/check362/check.go
- pcidss32/check712/check.go
- pcidss32/check112/check.go
- pcidss32/check225/check.go
- pcidss32/check134/check.go
- pcidss32/check722/check.go
- pcidss32/check723/check.go
- pcidss32/check21/check.go
- pcidss32/check23/check.go
- pcidss32/check135/check.go
- pcidss32/check132/check.go
- pcidss32/check61/check.go
- pcidss32/check1121/check.go
- pcidss32/check114/check.go
- pcidss32/check121/check.go
- pcidss32/check656/check.go
- pcidss32/check713/check.go
- nist800-190/check435/check.go
- nist800-190/check411/check.go
- nist800-190/check412/check.go
- nist800-190/check433/check.go
- nist800-190/check442/check.go
- nist800-190/check414/check.go
- nist800-190/check444/check.go
- nist800-190/check432/check.go
- nist800-190/check422/check.go
- nist800-190/check455/check.go
- nist800-190/check451/check.go
- nist800-190/check431/check.go
- nist800-190/check443/check.go
- hipaa_164/check312e/check.go
- hipaa_164/check312c/check.go
- hipaa_164/check308a7iie/check.go
- hipaa_164/check306e/check.go
- hipaa_164/check312e1/check.go
- hipaa_164/check316b2iii/check.go
- hipaa_164/check308a1i/check.go
- hipaa_164/check308a1iia/check.go
- hipaa_164/check308a5iib/check.go
- hipaa_164/check308a1iib/check.go
- hipaa_164/check310a1/check.go
- hipaa_164/check308a6ii/check.go
- hipaa_164/check308a3iib/check.go
- hipaa_164/check308a4iib/check.go
- hipaa_164/check310a1a/check.go
- hipaa_164/check308a3iia/check.go
- hipaa_164/check314a2ic/check.go
- hipaa_164/check308a4/check.go
- hipaa_164/check310d/check.go

### pkg/compliance/checks/
- nist80053/check_ac_14/check.go
- nist80053/check_ac_3_7/check.go
- nist80053/check_cm_5/check.go
- nist80053/check_ac_24/check.go
- pcidss32/check811/check.go
- pcidss32/check71/check.go
- pcidss32/check85/check.go
- pcidss32/check362/check.go
- pcidss32/check712/check.go
- pcidss32/check722/check.go
- pcidss32/check723/check.go
- pcidss32/check713/check.go
- nist800-190/check421/check.go
- nist800-190/check432/check.go
- nist800-190/check431/check.go
- hipaa_164/check312e1/check.go
- hipaa_164/check308a3iib/check.go
- hipaa_164/check308a4/check.go
- kubernetes/master_scheduler.go
- kubernetes/policies_network_cni.go
- kubernetes/policies_secrets_management.go
- kubernetes/kubelet_command.go
- kubernetes/worker_node_config.go
- kubernetes/policies_admission_control.go
- kubernetes/control_plane_config.go
- kubernetes/master_api_server.go
- kubernetes/policies_rbac.go
- kubernetes/policies_pod_security.go
- kubernetes/master_config.go
- kubernetes/etcd.go
- kubernetes/master_controller_manager.go
- kubernetes/policies_general.go
Loading
Loading