diff --git a/.gitignore b/.gitignore
index 7da9830..72f6da6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,6 @@
/mc-router
/dist/
/*.private.env.json
-/kustomization.yml
\ No newline at end of file
+/kustomization.yml
+
+/*.pem
\ No newline at end of file
diff --git a/.run/run (in docker).run.xml b/.run/run (in docker).run.xml
new file mode 100644
index 0000000..7bddb8f
--- /dev/null
+++ b/.run/run (in docker).run.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.run/run (in docker, auto scale).run.xml b/.run/run (in docker, auto scale).run.xml
new file mode 100644
index 0000000..bf632d9
--- /dev/null
+++ b/.run/run (in docker, auto scale).run.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 618a24d..9ee0fe5 100644
--- a/README.md
+++ b/README.md
@@ -744,6 +744,31 @@ then add the _Artifact Registry Reader_ Role to the _Compute Engine default serv
then use e.g. `gcloud auth configure-docker europe-docker.pkg.dev` or equivalent one time (to create a `~/.docker/config.json`),
and then use e.g. `--default-repo=europe-docker.pkg.dev/YOUR-PROJECT/YOUR-ARTIFACT-REGISTRY` option for `skaffold dev`.
+### Running in devcontainer
+
+This approach is useful for testing changes for [Docker auto scaling](#docker-auto-scale-updown).
+
+With IntelliJ Ultimate, [use these instructions](https://www.jetbrains.com/help/idea/start-dev-container-inside-ide.html). It is recommended to use the option to mount sources.
+
+
+
+Use the example compose file [in examples/docker-discovery](examples/docker-discovery/compose.yml) or similar with `network_mode` set to "bridge" to ensure that the mc-router instance running within the devcontainer can reach the backend servers.
+
+When applying the `mc-router.host` label to containers to be auto-discovered, it's easiest to use an external host of "localhost":
+
+```yaml
+ vanilla:
+ image: itzg/minecraft-server
+ environment:
+ EULA: "TRUE"
+ labels:
+ mc-router.host: "localhost"
+```
+
+Run one of the labeled services by clicking the run icon in the gutter.
+
+
+
### Performing snapshot release with Docker
```bash
diff --git a/docs/intellij-devcontainer.png b/docs/intellij-devcontainer.png
new file mode 100644
index 0000000..2bec066
Binary files /dev/null and b/docs/intellij-devcontainer.png differ
diff --git a/examples/docker-discovery/compose.yml b/examples/docker-discovery/compose.yml
index eabcb4c..3e16205 100644
--- a/examples/docker-discovery/compose.yml
+++ b/examples/docker-discovery/compose.yml
@@ -14,7 +14,7 @@ services:
environment:
EULA: "TRUE"
labels:
- mc-router.host: "localhost.itzg.me"
+ mc-router.host: "localhost"
# To allow for routing from devcontainer
network_mode: bridge
paper:
diff --git a/server/configs.go b/server/configs.go
index 2c82cfe..b688ca7 100644
--- a/server/configs.go
+++ b/server/configs.go
@@ -8,11 +8,11 @@ type WebhookConfig struct {
}
type AutoScale struct {
- Up bool `usage:"Scale from zero on access. For Kubernetes, increases StatefulSet replicas from 0 to 1. For Docker, starts or unpauses the container when accessed"`
- Down bool `default:"false" usage:"Scale to zero after idle. For Kubernetes, decreases StatefulSet replicas from 1 to 0. For Docker, gracefully stops the container when there are no connections"`
- DownAfter string `default:"10m" usage:"Server scale down delay after there are no connections"`
- AllowDeny string `usage:"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"`
- AsleepMOTD string `usage:"MOTD to display when auto-scaled down servers are accessed; if empty, no status will be served"`
+ Up bool `usage:"Scale from zero on access. For Kubernetes, increases StatefulSet replicas from 0 to 1. For Docker, starts or unpauses the container when accessed"`
+ Down bool `default:"false" usage:"Scale to zero after idle. For Kubernetes, decreases StatefulSet replicas from 1 to 0. For Docker, gracefully stops the container when there are no connections"`
+ DownAfter time.Duration `default:"10m" usage:"Server scale down delay after there are no connections"`
+ AllowDeny string `usage:"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"`
+ AsleepMOTD string `usage:"MOTD to display when auto-scaled down servers are accessed; if empty, no status will be served"`
}
type RoutesConfig struct {
@@ -37,9 +37,9 @@ type Config struct {
KubeNamespace string `usage:"The namespace to watch or blank for all, which is the default"`
InDocker bool `usage:"Use Docker service discovery"`
InDockerSwarm bool `usage:"Use Docker Swarm service discovery"`
- 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"`
+ DockerSocket string `usage:"Path to Docker socket to use"`
+ DockerTimeout time.Duration `usage:"Timeout (as duration) for the Docker integrations"`
+ DockerRefreshInterval time.Duration `default:"15s" usage:"Refresh interval (as duration) 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
diff --git a/server/docker.go b/server/docker.go
index 8c144c0..e29330c 100644
--- a/server/docker.go
+++ b/server/docker.go
@@ -29,12 +29,12 @@ const (
)
type dockerWatcherConfig struct {
- autoScaleUp bool
- autoScaleDown bool
- socket string
- timeoutSeconds int
- refreshIntervalSeconds int
- apiVersion string
+ autoScaleUp bool
+ autoScaleDown bool
+ socket string
+ timeout time.Duration
+ refreshInterval time.Duration
+ apiVersion string
}
func (c *dockerWatcherConfig) apiVersionOpt() client.Opt {
@@ -47,15 +47,15 @@ func (c *dockerWatcherConfig) apiVersionOpt() client.Opt {
}
}
-func NewDockerWatcher(socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool, dockerApiVersion string) IDockerWatcher {
+func NewDockerWatcher(socket string, timeout time.Duration, refreshInterval time.Duration, autoScaleUp bool, autoScaleDown bool, dockerApiVersion string) IDockerWatcher {
return &dockerWatcherImpl{
config: dockerWatcherConfig{
- socket: socket,
- timeoutSeconds: timeoutSeconds,
- refreshIntervalSeconds: refreshIntervalSeconds,
- autoScaleUp: autoScaleUp,
- autoScaleDown: autoScaleDown,
- apiVersion: dockerApiVersion,
+ socket: socket,
+ timeout: timeout,
+ refreshInterval: refreshInterval,
+ autoScaleUp: autoScaleUp,
+ autoScaleDown: autoScaleDown,
+ apiVersion: dockerApiVersion,
},
}
}
@@ -225,24 +225,25 @@ func (w *dockerWatcherImpl) monitorContainers(ctx context.Context) error {
func (w *dockerWatcherImpl) Start(ctx context.Context) error {
var err error
- timeout := time.Duration(w.config.timeoutSeconds) * time.Second
- refreshInterval := time.Duration(w.config.refreshIntervalSeconds) * time.Second
-
opts := []client.Opt{
- client.WithHost(w.config.socket),
- client.WithTimeout(timeout),
+ client.FromEnv,
+ client.WithTimeout(w.config.timeout),
client.WithHTTPHeaders(map[string]string{
"User-Agent": "mc-router ",
}),
w.config.apiVersionOpt(),
}
+ if w.config.socket != "" {
+ opts = append(opts, client.WithHost(w.config.socket))
+ }
w.client, err = client.NewClientWithOpts(opts...)
if err != nil {
return err
}
- ticker := time.NewTicker(refreshInterval)
+ // TODO: replace all this with events listening
+ ticker := time.NewTicker(w.config.refreshInterval)
logrus.Trace("Performing initial listing of Docker containers")
initialContainers, err := w.listContainers(ctx)
diff --git a/server/docker_swarm.go b/server/docker_swarm.go
index bbe1046..319d25e 100644
--- a/server/docker_swarm.go
+++ b/server/docker_swarm.go
@@ -19,15 +19,15 @@ import (
"github.com/sirupsen/logrus"
)
-func NewDockerSwarmWatcher(socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool, dockerApiVersion string) IDockerWatcher {
+func NewDockerSwarmWatcher(socket string, timeout time.Duration, refreshInterval time.Duration, autoScaleUp bool, autoScaleDown bool, dockerApiVersion string) IDockerWatcher {
return &dockerSwarmWatcherImpl{
config: dockerWatcherConfig{
- socket: socket,
- timeoutSeconds: timeoutSeconds,
- refreshIntervalSeconds: refreshIntervalSeconds,
- autoScaleUp: autoScaleUp,
- autoScaleDown: autoScaleDown,
- apiVersion: dockerApiVersion,
+ socket: socket,
+ timeout: timeout,
+ refreshInterval: refreshInterval,
+ autoScaleUp: autoScaleUp,
+ autoScaleDown: autoScaleDown,
+ apiVersion: dockerApiVersion,
},
}
}
@@ -61,12 +61,9 @@ func (w *dockerSwarmWatcherImpl) makeSleeperFunc(_ *routableService) SleeperFunc
func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
var err error
- timeout := time.Duration(w.config.timeoutSeconds) * time.Second
- refreshInterval := time.Duration(w.config.refreshIntervalSeconds) * time.Second
-
opts := []client.Opt{
client.WithHost(w.config.socket),
- client.WithTimeout(timeout),
+ client.WithTimeout(w.config.timeout),
client.WithHTTPHeaders(map[string]string{
"User-Agent": "mc-router ",
}),
@@ -78,7 +75,7 @@ func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
return err
}
- ticker := time.NewTicker(refreshInterval)
+ ticker := time.NewTicker(w.config.refreshInterval)
serviceMap := map[string]*routableService{}
logrus.Trace("Performing initial listing of Docker containers")
diff --git a/server/down_scaler.go b/server/down_scaler.go
index 0df1007..476e1c5 100644
--- a/server/down_scaler.go
+++ b/server/down_scaler.go
@@ -56,7 +56,6 @@ func (ds *downScalerImpl) Begin(backendEndpoint string) {
scaleDownCancel()
}
- logrus.WithField("backendEndpoint", backendEndpoint).Debug("Beginning scale down")
scaleDownContext, scaleDownContextCancellation := context.WithCancel(ds.parentContext)
ds.contextCancellations[backendEndpoint] = scaleDownContextCancellation
go ds.scaleDown(scaleDownContext, backendEndpoint)
@@ -78,12 +77,18 @@ func (ds *downScalerImpl) Cancel(backendEndpoint string) {
}
func (ds *downScalerImpl) scaleDown(ctx context.Context, backendEndpoint string) {
+ logrus.WithField("backendEndpoint", backendEndpoint).
+ WithField("delay", ds.delay).
+ Debug("Starting scale-down timer")
for {
select {
case <-ctx.Done():
return
case <-time.After(ds.delay):
sleepers := Routes.GetSleepers(backendEndpoint)
+ logrus.WithField("backendEndpoint", backendEndpoint).
+ WithField("sleepers", len(sleepers)).
+ Debug("Found sleepers to use")
if len(sleepers) == 0 {
return
}
diff --git a/server/server.go b/server/server.go
index 757c1ab..e412ea5 100644
--- a/server/server.go
+++ b/server/server.go
@@ -7,7 +7,6 @@ import (
"os"
"runtime/pprof"
"strconv"
- "time"
"github.com/sirupsen/logrus"
)
@@ -50,10 +49,7 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) {
metricsBuilder := NewMetricsBuilder(config.MetricsBackend, &config.MetricsBackendConfig)
downScalerEnabled := config.AutoScale.Down && (config.InKubeCluster || config.KubeConfig != "" || config.InDocker)
- downScalerDelay, err := time.ParseDuration(config.AutoScale.DownAfter)
- if err != nil {
- return nil, fmt.Errorf("could not parse auto-scale-down-after duration: %w", err)
- }
+ downScalerDelay := config.AutoScale.DownAfter
// Only one instance should be created
DownScaler = NewDownScaler(ctx, downScalerEnabled, downScalerDelay)