[self-hosted] add netbird server (#5232)

* Unified NetBird combined server (Management, Signal, Relay, STUN) as a single executable with richer YAML configuration, validation, and defaults.
  * Official Dockerfile/image for single-container deployment.
  * Optional in-process profiling endpoint for diagnostics.
  * Multiplexing to route HTTP/gRPC/WebSocket traffic via one port; runtime hooks to inject custom handlers.
* **Chores**
  * Updated deployment scripts, compose files, and reverse-proxy templates to target the combined server; added example configs and getting-started updates.
This commit is contained in:
Misha Bragin
2026-02-12 19:24:43 +01:00
committed by GitHub
parent 69d4b5d821
commit 64b849c801
23 changed files with 2198 additions and 603 deletions

View File

@@ -40,7 +40,6 @@ func Execute() error {
func init() {
stopCh = make(chan int)
defaultLogFile = "/var/log/netbird/signal.log"
defaultSignalSSLDir = "/var/lib/netbird/"
if runtime.GOOS == "windows" {
defaultLogFile = os.Getenv("PROGRAMDATA") + "\\Netbird\\" + "signal.log"

View File

@@ -18,7 +18,7 @@ import (
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
"github.com/netbirdio/netbird/signal/metrics"
"github.com/netbirdio/netbird/shared/metrics"
"github.com/netbirdio/netbird/encryption"
"github.com/netbirdio/netbird/shared/signal/proto"
@@ -38,13 +38,13 @@ import (
const legacyGRPCPort = 10000
var (
signalPort int
metricsPort int
signalLetsencryptDomain string
signalSSLDir string
defaultSignalSSLDir string
signalCertFile string
signalCertKey string
signalPort int
metricsPort int
signalLetsencryptDomain string
signalLetsencryptEmail string
signalLetsencryptDataDir string
signalCertFile string
signalCertKey string
signalKaep = grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: 5 * time.Second,
@@ -216,7 +216,7 @@ func getTLSConfigurations() ([]grpc.ServerOption, *autocert.Manager, *tls.Config
}
if signalLetsencryptDomain != "" {
certManager, err = encryption.CreateCertManager(signalSSLDir, signalLetsencryptDomain)
certManager, err = encryption.CreateCertManager(signalLetsencryptDataDir, signalLetsencryptDomain)
if err != nil {
return nil, certManager, nil, err
}
@@ -326,9 +326,11 @@ func loadTLSConfig(certFile string, certKey string) (*tls.Config, error) {
func init() {
runCmd.PersistentFlags().IntVar(&signalPort, "port", 80, "Server port to listen on (defaults to 443 if TLS is enabled, 80 otherwise")
runCmd.Flags().IntVar(&metricsPort, "metrics-port", 9090, "metrics endpoint http port. Metrics are accessible under host:metrics-port/metrics")
runCmd.Flags().StringVar(&signalSSLDir, "ssl-dir", defaultSignalSSLDir, "server ssl directory location. *Required only for Let's Encrypt certificates.")
runCmd.Flags().StringVar(&signalLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
runCmd.Flags().StringVar(&signalCertFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
runCmd.Flags().StringVar(&signalCertKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
runCmd.PersistentFlags().StringVar(&signalLetsencryptDataDir, "letsencrypt-data-dir", "", "a directory to store Let's Encrypt data. Required if Let's Encrypt is enabled.")
runCmd.PersistentFlags().StringVar(&signalLetsencryptDataDir, "ssl-dir", "", "server ssl directory location. *Required only for Let's Encrypt certificates. Deprecated: use --letsencrypt-data-dir")
runCmd.PersistentFlags().StringVar(&signalLetsencryptDomain, "letsencrypt-domain", "", "a domain to issue Let's Encrypt certificate for. Enables TLS using Let's Encrypt. Will fetch and renew certificate, and run the server with TLS")
runCmd.PersistentFlags().StringVar(&signalLetsencryptEmail, "letsencrypt-email", "", "email address to use for Let's Encrypt certificate registration")
runCmd.PersistentFlags().StringVar(&signalCertFile, "cert-file", "", "Location of your SSL certificate. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
runCmd.PersistentFlags().StringVar(&signalCertKey, "cert-key", "", "Location of your SSL certificate private key. Can be used when you have an existing certificate and don't want a new certificate be generated automatically. If letsencrypt-domain is specified this property has no effect")
setFlagsFromEnvVars(runCmd)
}

View File

@@ -24,15 +24,19 @@ type AppMetrics struct {
MessageSize metric.Int64Histogram
}
func NewAppMetrics(meter metric.Meter) (*AppMetrics, error) {
activePeers, err := meter.Int64UpDownCounter("active_peers",
func NewAppMetrics(meter metric.Meter, prefix ...string) (*AppMetrics, error) {
p := ""
if len(prefix) > 0 {
p = prefix[0]
}
activePeers, err := meter.Int64UpDownCounter(p+"active_peers",
metric.WithDescription("Number of active connected peers"),
)
if err != nil {
return nil, err
}
peerConnectionDuration, err := meter.Int64Histogram("peer_connection_duration_seconds",
peerConnectionDuration, err := meter.Int64Histogram(p+"peer_connection_duration_seconds",
metric.WithExplicitBucketBoundaries(getPeerConnectionDurationBucketBoundaries()...),
metric.WithDescription("Duration of how long a peer was connected"),
)
@@ -40,28 +44,28 @@ func NewAppMetrics(meter metric.Meter) (*AppMetrics, error) {
return nil, err
}
registrations, err := meter.Int64Counter("registrations_total",
registrations, err := meter.Int64Counter(p+"registrations_total",
metric.WithDescription("Total number of peer registrations"),
)
if err != nil {
return nil, err
}
deregistrations, err := meter.Int64Counter("deregistrations_total",
deregistrations, err := meter.Int64Counter(p+"deregistrations_total",
metric.WithDescription("Total number of peer deregistrations"),
)
if err != nil {
return nil, err
}
registrationFailures, err := meter.Int64Counter("registration_failures_total",
registrationFailures, err := meter.Int64Counter(p+"registration_failures_total",
metric.WithDescription("Total number of peer registration failures"),
)
if err != nil {
return nil, err
}
registrationDelay, err := meter.Float64Histogram("registration_delay_milliseconds",
registrationDelay, err := meter.Float64Histogram(p+"registration_delay_milliseconds",
metric.WithExplicitBucketBoundaries(getStandardBucketBoundaries()...),
metric.WithDescription("Duration of how long it takes to register a peer"),
)
@@ -69,7 +73,7 @@ func NewAppMetrics(meter metric.Meter) (*AppMetrics, error) {
return nil, err
}
getRegistrationDelay, err := meter.Float64Histogram("get_registration_delay_milliseconds",
getRegistrationDelay, err := meter.Float64Histogram(p+"get_registration_delay_milliseconds",
metric.WithExplicitBucketBoundaries(getStandardBucketBoundaries()...),
metric.WithDescription("Duration of how long it takes to load a connection from the registry"),
)
@@ -77,21 +81,21 @@ func NewAppMetrics(meter metric.Meter) (*AppMetrics, error) {
return nil, err
}
messagesForwarded, err := meter.Int64Counter("messages_forwarded_total",
messagesForwarded, err := meter.Int64Counter(p+"messages_forwarded_total",
metric.WithDescription("Total number of messages forwarded to peers"),
)
if err != nil {
return nil, err
}
messageForwardFailures, err := meter.Int64Counter("message_forward_failures_total",
messageForwardFailures, err := meter.Int64Counter(p+"message_forward_failures_total",
metric.WithDescription("Total number of message forwarding failures"),
)
if err != nil {
return nil, err
}
messageForwardLatency, err := meter.Float64Histogram("message_forward_latency_milliseconds",
messageForwardLatency, err := meter.Float64Histogram(p+"message_forward_latency_milliseconds",
metric.WithExplicitBucketBoundaries(getStandardBucketBoundaries()...),
metric.WithDescription("Duration of how long it takes to forward a message to a peer"),
)
@@ -100,7 +104,7 @@ func NewAppMetrics(meter metric.Meter) (*AppMetrics, error) {
}
messageSize, err := meter.Int64Histogram(
"message.size.bytes",
p+"message.size.bytes",
metric.WithUnit("bytes"),
metric.WithExplicitBucketBoundaries(getMessageSizeBucketBoundaries()...),
metric.WithDescription("Records the size of each message sent"),

View File

@@ -1,74 +0,0 @@
package metrics
import (
"context"
"fmt"
"net/http"
"reflect"
prometheus2 "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/prometheus"
api "go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/sdk/metric"
)
const defaultEndpoint = "/metrics"
// Metrics holds the metrics information and exposes it
type Metrics struct {
Meter api.Meter
provider *metric.MeterProvider
Endpoint string
*http.Server
}
// NewServer initializes and returns a new Metrics instance
func NewServer(port int, endpoint string) (*Metrics, error) {
exporter, err := prometheus.New()
if err != nil {
return nil, err
}
provider := metric.NewMeterProvider(metric.WithReader(exporter))
otel.SetMeterProvider(provider)
pkg := reflect.TypeOf(defaultEndpoint).PkgPath()
meter := provider.Meter(pkg)
if endpoint == "" {
endpoint = defaultEndpoint
}
router := http.NewServeMux()
router.Handle(endpoint, promhttp.HandlerFor(
prometheus2.DefaultGatherer,
promhttp.HandlerOpts{EnableOpenMetrics: true}))
server := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: router,
}
return &Metrics{
Meter: meter,
provider: provider,
Endpoint: endpoint,
Server: server,
}, nil
}
// Shutdown stops the metrics server
func (m *Metrics) Shutdown(ctx context.Context) error {
if err := m.Server.Shutdown(ctx); err != nil {
return fmt.Errorf("http server: %w", err)
}
if err := m.provider.Shutdown(ctx); err != nil {
return fmt.Errorf("meter provider: %w", err)
}
return nil
}

View File

@@ -62,8 +62,8 @@ type Server struct {
}
// NewServer creates a new Signal server
func NewServer(ctx context.Context, meter metric.Meter) (*Server, error) {
appMetrics, err := metrics.NewAppMetrics(meter)
func NewServer(ctx context.Context, meter metric.Meter, metricsPrefix ...string) (*Server, error) {
appMetrics, err := metrics.NewAppMetrics(meter, metricsPrefix...)
if err != nil {
return nil, fmt.Errorf("creating app metrics: %v", err)
}