Use Docker API version auto negotiation (#486)

This commit is contained in:
Geoff Bourne
2025-11-28 11:20:10 -06:00
committed by GitHub
parent ecbc6638d2
commit b67d0985dc
5 changed files with 79 additions and 40 deletions
+12 -8
View File
@@ -23,14 +23,14 @@ Some other features included:
```text ```text
-api-binding host:port -api-binding host:port
The host:port bound for servicing API requests (env API_BINDING) 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 -auto-scale-up
Increase Kubernetes StatefulSet Replicas (only) from 0 to 1 on respective backend servers when accessed (env 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 -clients-to-allow value
Zero or more client IP addresses or CIDRs to allow. Takes precedence over deny. (env CLIENTS_TO_ALLOW) Zero or more client IP addresses or CIDRs to allow. Takes precedence over deny. (env CLIENTS_TO_ALLOW)
-clients-to-deny value -clients-to-deny value
@@ -43,6 +43,8 @@ Some other features included:
Enable debug logs (env DEBUG) Enable debug logs (env DEBUG)
-default string -default string
host:port of a default Minecraft server to use when mapping not found (env DEFAULT) 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 -docker-refresh-interval int
Refresh interval in seconds for the Docker integrations (env DOCKER_REFRESH_INTERVAL) (default 15) Refresh interval in seconds for the Docker integrations (env DOCKER_REFRESH_INTERVAL) (default 15)
-docker-socket string -docker-socket string
@@ -85,12 +87,16 @@ Some other features included:
The port bound to listen for Minecraft client connections (env PORT) (default 25565) The port bound to listen for Minecraft client connections (env PORT) (default 25565)
-receive-proxy-protocol -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) 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 -routes-config path
Name or full path to routes config file (env ROUTES_CONFIG) Name or full path to routes config file (env ROUTES_CONFIG)
-routes-config-watch -routes-config-watch
Watch for config file changes (env ROUTES_CONFIG_WATCH) Watch for config file changes (env ROUTES_CONFIG_WATCH)
-simplify-srv -simplify-srv
Simplify fully qualified SRV records for mapping (env SIMPLIFY_SRV) Simplify fully qualified SRV records for mapping (env SIMPLIFY_SRV)
-trace
Enable trace logs (env TRACE)
-trusted-proxies value -trusted-proxies value
Comma delimited list of CIDR notation IP blocks to trust when receiving PROXY protocol (env TRUSTED_PROXIES) Comma delimited list of CIDR notation IP blocks to trust when receiving PROXY protocol (env TRUSTED_PROXIES)
-use-proxy-protocol -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) 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 -webhook-url string
If set, a POST request that contains connection status notifications will be sent to this HTTP address (env WEBHOOK_URL) 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 ## Docker Multi-Architecture Image
+1
View File
@@ -37,6 +37,7 @@ type Config struct {
DockerSocket string `default:"unix:///var/run/docker.sock" usage:"Path to Docker socket to use"` 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"` 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"` 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"` MetricsBackend string `default:"discard" usage:"Backend to use for metrics exposure/publishing: discard,expvar,influxdb,prometheus"`
MetricsBackendConfig MetricsBackendConfig MetricsBackendConfig MetricsBackendConfig
UseProxyProtocol bool `default:"false" usage:"Send PROXY protocol to backend servers"` UseProxyProtocol bool `default:"false" usage:"Send PROXY protocol to backend servers"`
+41 -16
View File
@@ -15,28 +15,56 @@ import (
) )
type IDockerWatcher interface { type IDockerWatcher interface {
Start(ctx context.Context, socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error Start(ctx context.Context) error
} }
const ( const (
DockerAPIVersion = "1.24"
DockerRouterLabelHost = "mc-router.host" DockerRouterLabelHost = "mc-router.host"
DockerRouterLabelPort = "mc-router.port" DockerRouterLabelPort = "mc-router.port"
DockerRouterLabelDefault = "mc-router.default" DockerRouterLabelDefault = "mc-router.default"
DockerRouterLabelNetwork = "mc-router.network" 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 { type dockerWatcherImpl struct {
sync.RWMutex sync.RWMutex
autoScaleUp bool config dockerWatcherConfig
autoScaleDown bool client *client.Client
client *client.Client
} }
func (w *dockerWatcherImpl) makeWakerFunc(_ *routableContainer) ScalerFunc { func (w *dockerWatcherImpl) makeWakerFunc(_ *routableContainer) ScalerFunc {
if !w.autoScaleUp { if !w.config.autoScaleUp {
return nil return nil
} }
return func(ctx context.Context) error { return func(ctx context.Context) error {
@@ -46,7 +74,7 @@ func (w *dockerWatcherImpl) makeWakerFunc(_ *routableContainer) ScalerFunc {
} }
func (w *dockerWatcherImpl) makeSleeperFunc(_ *routableContainer) ScalerFunc { func (w *dockerWatcherImpl) makeSleeperFunc(_ *routableContainer) ScalerFunc {
if !w.autoScaleDown { if !w.config.autoScaleDown {
return nil return nil
} }
return func(ctx context.Context) error { 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 var err error
w.autoScaleUp = autoScaleUp timeout := time.Duration(w.config.timeoutSeconds) * time.Second
w.autoScaleDown = autoScaleDown refreshInterval := time.Duration(w.config.refreshIntervalSeconds) * time.Second
timeout := time.Duration(timeoutSeconds) * time.Second
refreshInterval := time.Duration(refreshIntervalSeconds) * time.Second
opts := []client.Opt{ opts := []client.Opt{
client.WithHost(socket), client.WithHost(w.config.socket),
client.WithTimeout(timeout), client.WithTimeout(timeout),
client.WithHTTPHeaders(map[string]string{ client.WithHTTPHeaders(map[string]string{
"User-Agent": "mc-router ", "User-Agent": "mc-router ",
}), }),
client.WithVersion(DockerAPIVersion), w.config.apiVersionOpt(),
} }
w.client, err = client.NewClientWithOpts(opts...) w.client, err = client.NewClientWithOpts(opts...)
+21 -14
View File
@@ -19,17 +19,27 @@ import (
"github.com/sirupsen/logrus" "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 { type dockerSwarmWatcherImpl struct {
sync.RWMutex sync.RWMutex
autoScaleUp bool config dockerWatcherConfig
autoScaleDown bool client *client.Client
client *client.Client
} }
func (w *dockerSwarmWatcherImpl) makeWakerFunc(_ *routableService) ScalerFunc { func (w *dockerSwarmWatcherImpl) makeWakerFunc(_ *routableService) ScalerFunc {
if !w.autoScaleUp { if !w.config.autoScaleUp {
return nil return nil
} }
return func(ctx context.Context) error { return func(ctx context.Context) error {
@@ -39,7 +49,7 @@ func (w *dockerSwarmWatcherImpl) makeWakerFunc(_ *routableService) ScalerFunc {
} }
func (w *dockerSwarmWatcherImpl) makeSleeperFunc(_ *routableService) ScalerFunc { func (w *dockerSwarmWatcherImpl) makeSleeperFunc(_ *routableService) ScalerFunc {
if !w.autoScaleDown { if !w.config.autoScaleDown {
return nil return nil
} }
return func(ctx context.Context) error { 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 var err error
w.autoScaleUp = autoScaleUp timeout := time.Duration(w.config.timeoutSeconds) * time.Second
w.autoScaleDown = autoScaleDown refreshInterval := time.Duration(w.config.refreshIntervalSeconds) * time.Second
timeout := time.Duration(timeoutSeconds) * time.Second
refreshInterval := time.Duration(refreshIntervalSeconds) * time.Second
opts := []client.Opt{ opts := []client.Opt{
client.WithHost(socket), client.WithHost(w.config.socket),
client.WithTimeout(timeout), client.WithTimeout(timeout),
client.WithHTTPHeaders(map[string]string{ client.WithHTTPHeaders(map[string]string{
"User-Agent": "mc-router ", "User-Agent": "mc-router ",
}), }),
client.WithVersion(DockerAPIVersion), client.WithAPIVersionNegotiation(),
} }
w.client, err = client.NewClientWithOpts(opts...) w.client, err = client.NewClientWithOpts(opts...)
+4 -2
View File
@@ -143,7 +143,8 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) {
// TODO convert to RouteFinder // TODO convert to RouteFinder
if config.InDocker { 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 { if err != nil {
return nil, fmt.Errorf("could not start docker integration: %w", err) 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 // TODO convert to RouteFinder
if config.InDockerSwarm { 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 { if err != nil {
return nil, fmt.Errorf("could not start docker swarm integration: %w", err) return nil, fmt.Errorf("could not start docker swarm integration: %w", err)
} }