mirror of
https://github.com/unpoller/unpoller.git
synced 2026-04-05 08:54:09 -04:00
Fix health check port binding conflict (issue #892)
The Docker health check was attempting to bind to ports already in use by the running application, causing "address already in use" errors. This fix adds a health check mode that skips network binding operations while still validating output configuration (listen addresses, paths, etc.). Changes: - Add health check mode flag in pkg/poller/outputs.go - Update prometheus and webserver DebugOutput() to skip port binding in health check mode - Maintain full configuration validation without network conflicts Fixes #892 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -127,6 +127,10 @@ func (u *UnifiPoller) DebugIO() error {
|
|||||||
// It validates configuration and checks if inputs/outputs are properly configured.
|
// It validates configuration and checks if inputs/outputs are properly configured.
|
||||||
// Returns nil (exit 0) if healthy, error (exit 1) if unhealthy.
|
// Returns nil (exit 0) if healthy, error (exit 1) if unhealthy.
|
||||||
func (u *UnifiPoller) HealthCheck() error {
|
func (u *UnifiPoller) HealthCheck() error {
|
||||||
|
// Enable health check mode to skip network binding in output validation.
|
||||||
|
SetHealthCheckMode(true)
|
||||||
|
defer SetHealthCheckMode(false)
|
||||||
|
|
||||||
// Silence output for health checks (Docker doesn't need verbose logs).
|
// Silence output for health checks (Docker doesn't need verbose logs).
|
||||||
u.Quiet = true
|
u.Quiet = true
|
||||||
|
|
||||||
@@ -170,7 +174,8 @@ func (u *UnifiPoller) HealthCheck() error {
|
|||||||
return fmt.Errorf("health check failed: no enabled output plugins")
|
return fmt.Errorf("health check failed: no enabled output plugins")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform basic validation checks on enabled outputs.
|
// Perform configuration validation on enabled outputs.
|
||||||
|
// Network binding checks will be skipped automatically due to health check mode.
|
||||||
for _, output := range outputs {
|
for _, output := range outputs {
|
||||||
if !output.Enabled() {
|
if !output.Enabled() {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ var (
|
|||||||
outputSync sync.RWMutex // nolint: gochecknoglobals
|
outputSync sync.RWMutex // nolint: gochecknoglobals
|
||||||
errNoOutputPlugins = fmt.Errorf("no output plugins imported")
|
errNoOutputPlugins = fmt.Errorf("no output plugins imported")
|
||||||
errAllOutputStopped = fmt.Errorf("all output plugins have stopped, or none enabled")
|
errAllOutputStopped = fmt.Errorf("all output plugins have stopped, or none enabled")
|
||||||
|
// healthCheckMode indicates when we're running in health check mode to skip network operations
|
||||||
|
healthCheckMode bool // nolint: gochecknoglobals
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collect is passed into output packages so they may collect metrics to output.
|
// Collect is passed into output packages so they may collect metrics to output.
|
||||||
@@ -50,6 +52,17 @@ func NewOutput(o *Output) {
|
|||||||
outputs = append(outputs, o)
|
outputs = append(outputs, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetHealthCheckMode enables or disables health check mode.
|
||||||
|
// When enabled, output plugins should skip network operations in DebugOutput().
|
||||||
|
func SetHealthCheckMode(enabled bool) {
|
||||||
|
healthCheckMode = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsHealthCheckMode returns true if we're running in health check mode.
|
||||||
|
func IsHealthCheckMode() bool {
|
||||||
|
return healthCheckMode
|
||||||
|
}
|
||||||
|
|
||||||
// Poller returns the poller config.
|
// Poller returns the poller config.
|
||||||
func (u *UnifiPoller) Poller() Poller {
|
func (u *UnifiPoller) Poller() Poller {
|
||||||
return *u.Config.Poller
|
return *u.Config.Poller
|
||||||
|
|||||||
@@ -139,6 +139,12 @@ func (u *promUnifi) DebugOutput() (bool, error) {
|
|||||||
return false, fmt.Errorf("invalid listen address: %s (must be of the form \"IP:Port\"", u.HTTPListen)
|
return false, fmt.Errorf("invalid listen address: %s (must be of the form \"IP:Port\"", u.HTTPListen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip network binding check during health checks to avoid "address already in use"
|
||||||
|
// errors when the main application is already running and bound to the port.
|
||||||
|
if poller.IsHealthCheckMode() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", u.HTTPListen)
|
ln, err := net.Listen("tcp", u.HTTPListen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|||||||
@@ -103,11 +103,11 @@ func (s *Server) DebugOutput() (bool, error) {
|
|||||||
if s == nil {
|
if s == nil {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !s.Enabled() {
|
if !s.Enabled() {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.HTMLPath == "" {
|
if s.HTMLPath == "" {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -117,6 +117,12 @@ func (s *Server) DebugOutput() (bool, error) {
|
|||||||
return false, fmt.Errorf("problem with HTML path: %w", err)
|
return false, fmt.Errorf("problem with HTML path: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip network binding check during health checks to avoid "address already in use"
|
||||||
|
// errors when the main application is already running and bound to the port.
|
||||||
|
if poller.IsHealthCheckMode() {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
// check the port
|
// check the port
|
||||||
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Port))
|
ln, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Port))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user