diff --git a/README.md b/README.md index bdf8ecf..bf2db64 100644 --- a/README.md +++ b/README.md @@ -23,14 +23,14 @@ Some other features included: ```text -api-binding host:port The host:port bound for servicing API requests (env API_BINDING) + -auto-scale-allow-deny string + Path to config for server allowlists and denylists. If a global/server entry is specified, only players allowed to connect to the server will be able to trigger a scale up when -auto-scale-up is enabled or cancel active down scalers when -auto-scale-down is enabled (env AUTO_SCALE_ALLOW_DENY) + -auto-scale-down + Decrease Kubernetes StatefulSet Replicas (only) from 1 to 0 on respective backend servers after there are no connections (env AUTO_SCALE_DOWN) + -auto-scale-down-after string + Server scale down delay after there are no connections (env AUTO_SCALE_DOWN_AFTER) (default "10m") -auto-scale-up Increase Kubernetes StatefulSet Replicas (only) from 0 to 1 on respective backend servers when accessed (env AUTO_SCALE_UP) - -auto-scale-down - Decrease Kubernetes StatefulSet Replicas (only) from 1 to 0 after all backend connections have stopped and a configurable amount of delay has passed (env AUTO_SCALE_DOWN) - -auto-scale-down-after - String indicating how long an auto scale down should wait before scaling down a backend server. If a player rejoins the server during this delay, the scale down will be canceled (env AUTO_SCALE_DOWN_AFTER) - -auto-scale-allow-deny string - Path to config for server allowlists and denylists. If a global/server entry is specified, only players allowed to connect to the server will be able to trigger a scale up when -auto-scale-up is enabled or cancel active down scalers when -auto-scale-down is enabled (env AUTO_SCALE_ALLOW_DENY) -clients-to-allow value Zero or more client IP addresses or CIDRs to allow. Takes precedence over deny. (env CLIENTS_TO_ALLOW) -clients-to-deny value @@ -43,6 +43,8 @@ Some other features included: Enable debug logs (env DEBUG) -default string host:port of a default Minecraft server to use when mapping not found (env DEFAULT) + -docker-api-version string + Instead of auto-negotiating, use specific Docker API version (env DOCKER_API_VERSION) -docker-refresh-interval int Refresh interval in seconds for the Docker integrations (env DOCKER_REFRESH_INTERVAL) (default 15) -docker-socket string @@ -85,12 +87,16 @@ Some other features included: The port bound to listen for Minecraft client connections (env PORT) (default 25565) -receive-proxy-protocol Receive PROXY protocol from backend servers, by default trusts every proxy header that it receives, combine with -trusted-proxies to specify a list of trusted proxies (env RECEIVE_PROXY_PROTOCOL) + -record-logins + Log and generate metrics on player logins. Metrics only supported with influxdb or prometheus backend (env RECORD_LOGINS) -routes-config path Name or full path to routes config file (env ROUTES_CONFIG) -routes-config-watch Watch for config file changes (env ROUTES_CONFIG_WATCH) -simplify-srv Simplify fully qualified SRV records for mapping (env SIMPLIFY_SRV) + -trace + Enable trace logs (env TRACE) -trusted-proxies value Comma delimited list of CIDR notation IP blocks to trust when receiving PROXY protocol (env TRUSTED_PROXIES) -use-proxy-protocol @@ -101,8 +107,6 @@ Some other features included: Indicates if the webhook will only be called if a user is connecting rather than just server list/ping (env WEBHOOK_REQUIRE_USER) -webhook-url string If set, a POST request that contains connection status notifications will be sent to this HTTP address (env WEBHOOK_URL) - -record-logins - Log and generate metrics on player logins. Metrics only supported with influxdb or prometheus backend (env RECORD_LOGINS) ``` ## Docker Multi-Architecture Image diff --git a/server/configs.go b/server/configs.go index 9e4b108..6e1039d 100644 --- a/server/configs.go +++ b/server/configs.go @@ -37,6 +37,7 @@ type Config struct { DockerSocket string `default:"unix:///var/run/docker.sock" usage:"Path to Docker socket to use"` DockerTimeout int `default:"0" usage:"Timeout configuration in seconds for the Docker integrations"` DockerRefreshInterval int `default:"15" usage:"Refresh interval in seconds for the Docker integrations"` + DockerApiVersion string `usage:"Instead of auto-negotiating, use specific Docker API version"` MetricsBackend string `default:"discard" usage:"Backend to use for metrics exposure/publishing: discard,expvar,influxdb,prometheus"` MetricsBackendConfig MetricsBackendConfig UseProxyProtocol bool `default:"false" usage:"Send PROXY protocol to backend servers"` diff --git a/server/docker.go b/server/docker.go index f3765f6..98879ce 100644 --- a/server/docker.go +++ b/server/docker.go @@ -15,28 +15,56 @@ import ( ) type IDockerWatcher interface { - Start(ctx context.Context, socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error + Start(ctx context.Context) error } const ( - DockerAPIVersion = "1.24" DockerRouterLabelHost = "mc-router.host" DockerRouterLabelPort = "mc-router.port" DockerRouterLabelDefault = "mc-router.default" DockerRouterLabelNetwork = "mc-router.network" ) -var DockerWatcher IDockerWatcher = &dockerWatcherImpl{} +type dockerWatcherConfig struct { + autoScaleUp bool + autoScaleDown bool + socket string + timeoutSeconds int + refreshIntervalSeconds int + apiVersion string +} + +func (c *dockerWatcherConfig) apiVersionOpt() client.Opt { + if c.apiVersion != "" { + logrus.WithField("apiVersion", c.apiVersion).Debug("Using specific Docker API version") + return client.WithVersion(c.apiVersion) + } else { + logrus.Debug("Using Docker API version negotiation") + return client.WithAPIVersionNegotiation() + } +} + +func NewDockerWatcher(socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool, dockerApiVersion string) IDockerWatcher { + return &dockerWatcherImpl{ + config: dockerWatcherConfig{ + socket: socket, + timeoutSeconds: timeoutSeconds, + refreshIntervalSeconds: refreshIntervalSeconds, + autoScaleUp: autoScaleUp, + autoScaleDown: autoScaleDown, + apiVersion: dockerApiVersion, + }, + } +} type dockerWatcherImpl struct { sync.RWMutex - autoScaleUp bool - autoScaleDown bool - client *client.Client + config dockerWatcherConfig + client *client.Client } func (w *dockerWatcherImpl) makeWakerFunc(_ *routableContainer) ScalerFunc { - if !w.autoScaleUp { + if !w.config.autoScaleUp { return nil } return func(ctx context.Context) error { @@ -46,7 +74,7 @@ func (w *dockerWatcherImpl) makeWakerFunc(_ *routableContainer) ScalerFunc { } func (w *dockerWatcherImpl) makeSleeperFunc(_ *routableContainer) ScalerFunc { - if !w.autoScaleDown { + if !w.config.autoScaleDown { return nil } return func(ctx context.Context) error { @@ -55,22 +83,19 @@ func (w *dockerWatcherImpl) makeSleeperFunc(_ *routableContainer) ScalerFunc { } } -func (w *dockerWatcherImpl) Start(ctx context.Context, socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error { +func (w *dockerWatcherImpl) Start(ctx context.Context) error { var err error - w.autoScaleUp = autoScaleUp - w.autoScaleDown = autoScaleDown - - timeout := time.Duration(timeoutSeconds) * time.Second - refreshInterval := time.Duration(refreshIntervalSeconds) * time.Second + timeout := time.Duration(w.config.timeoutSeconds) * time.Second + refreshInterval := time.Duration(w.config.refreshIntervalSeconds) * time.Second opts := []client.Opt{ - client.WithHost(socket), + client.WithHost(w.config.socket), client.WithTimeout(timeout), client.WithHTTPHeaders(map[string]string{ "User-Agent": "mc-router ", }), - client.WithVersion(DockerAPIVersion), + w.config.apiVersionOpt(), } w.client, err = client.NewClientWithOpts(opts...) diff --git a/server/docker_swarm.go b/server/docker_swarm.go index 8ddf053..e462d1d 100644 --- a/server/docker_swarm.go +++ b/server/docker_swarm.go @@ -19,17 +19,27 @@ import ( "github.com/sirupsen/logrus" ) -var DockerSwarmWatcher IDockerWatcher = &dockerSwarmWatcherImpl{} +func NewDockerSwarmWatcher(socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool, dockerApiVersion string) IDockerWatcher { + return &dockerSwarmWatcherImpl{ + config: dockerWatcherConfig{ + socket: socket, + timeoutSeconds: timeoutSeconds, + refreshIntervalSeconds: refreshIntervalSeconds, + autoScaleUp: autoScaleUp, + autoScaleDown: autoScaleDown, + apiVersion: dockerApiVersion, + }, + } +} type dockerSwarmWatcherImpl struct { sync.RWMutex - autoScaleUp bool - autoScaleDown bool - client *client.Client + config dockerWatcherConfig + client *client.Client } func (w *dockerSwarmWatcherImpl) makeWakerFunc(_ *routableService) ScalerFunc { - if !w.autoScaleUp { + if !w.config.autoScaleUp { return nil } return func(ctx context.Context) error { @@ -39,7 +49,7 @@ func (w *dockerSwarmWatcherImpl) makeWakerFunc(_ *routableService) ScalerFunc { } func (w *dockerSwarmWatcherImpl) makeSleeperFunc(_ *routableService) ScalerFunc { - if !w.autoScaleDown { + if !w.config.autoScaleDown { return nil } return func(ctx context.Context) error { @@ -48,22 +58,19 @@ func (w *dockerSwarmWatcherImpl) makeSleeperFunc(_ *routableService) ScalerFunc } } -func (w *dockerSwarmWatcherImpl) Start(ctx context.Context, socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error { +func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error { var err error - w.autoScaleUp = autoScaleUp - w.autoScaleDown = autoScaleDown - - timeout := time.Duration(timeoutSeconds) * time.Second - refreshInterval := time.Duration(refreshIntervalSeconds) * time.Second + timeout := time.Duration(w.config.timeoutSeconds) * time.Second + refreshInterval := time.Duration(w.config.refreshIntervalSeconds) * time.Second opts := []client.Opt{ - client.WithHost(socket), + client.WithHost(w.config.socket), client.WithTimeout(timeout), client.WithHTTPHeaders(map[string]string{ "User-Agent": "mc-router ", }), - client.WithVersion(DockerAPIVersion), + client.WithAPIVersionNegotiation(), } w.client, err = client.NewClientWithOpts(opts...) diff --git a/server/server.go b/server/server.go index 8a336fc..78ddd4e 100644 --- a/server/server.go +++ b/server/server.go @@ -143,7 +143,8 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) { // TODO convert to RouteFinder if config.InDocker { - err = DockerWatcher.Start(ctx, config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down) + watcher := NewDockerWatcher(config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down, config.DockerApiVersion) + err = watcher.Start(ctx) if err != nil { return nil, fmt.Errorf("could not start docker integration: %w", err) } @@ -151,7 +152,8 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) { // TODO convert to RouteFinder if config.InDockerSwarm { - err = DockerSwarmWatcher.Start(ctx, config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down) + watcher := NewDockerSwarmWatcher(config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down, config.DockerApiVersion) + err = watcher.Start(ctx) if err != nil { return nil, fmt.Errorf("could not start docker swarm integration: %w", err) }