Use duration type for options for Docker auto-scaling delays (#524)
This commit is contained in:
+3
-1
@@ -6,4 +6,6 @@
|
|||||||
/mc-router
|
/mc-router
|
||||||
/dist/
|
/dist/
|
||||||
/*.private.env.json
|
/*.private.env.json
|
||||||
/kustomization.yml
|
/kustomization.yml
|
||||||
|
|
||||||
|
/*.pem
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="run (in docker)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||||
|
<module name="mc-router" />
|
||||||
|
<working_directory value="$PROJECT_DIR$" />
|
||||||
|
<parameters value="--debug --in-docker" />
|
||||||
|
<EXTENSION ID="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
||||||
|
</ENTRIES>
|
||||||
|
</EXTENSION>
|
||||||
|
<kind value="DIRECTORY" />
|
||||||
|
<package value="github.com/itzg/mc-router" />
|
||||||
|
<directory value="$PROJECT_DIR$/cmd/mc-router" />
|
||||||
|
<filePath value="$PROJECT_DIR$/cmd/mc-router/main.go" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="run (in docker, auto scale)" type="GoApplicationRunConfiguration" factoryName="Go Application">
|
||||||
|
<module name="mc-router" />
|
||||||
|
<working_directory value="$PROJECT_DIR$" />
|
||||||
|
<parameters value="--mapping localhost=localhost:25566 --in-docker --docker-socket="" --auto-scale-up --auto-scale-down --auto-scale-down-after=30s" />
|
||||||
|
<EXTENSION ID="net.ashald.envfile">
|
||||||
|
<option name="IS_ENABLED" value="false" />
|
||||||
|
<option name="IS_SUBST" value="false" />
|
||||||
|
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
||||||
|
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
||||||
|
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
||||||
|
<ENTRIES>
|
||||||
|
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
||||||
|
</ENTRIES>
|
||||||
|
</EXTENSION>
|
||||||
|
<kind value="DIRECTORY" />
|
||||||
|
<package value="github.com/itzg/mc-router" />
|
||||||
|
<directory value="$PROJECT_DIR$/cmd/mc-router" />
|
||||||
|
<filePath value="$PROJECT_DIR$/cmd/mc-router/main.go" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
@@ -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`),
|
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`.
|
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
|
### Performing snapshot release with Docker
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 495 KiB |
@@ -14,7 +14,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
EULA: "TRUE"
|
EULA: "TRUE"
|
||||||
labels:
|
labels:
|
||||||
mc-router.host: "localhost.itzg.me"
|
mc-router.host: "localhost"
|
||||||
# To allow for routing from devcontainer
|
# To allow for routing from devcontainer
|
||||||
network_mode: bridge
|
network_mode: bridge
|
||||||
paper:
|
paper:
|
||||||
|
|||||||
+8
-8
@@ -8,11 +8,11 @@ type WebhookConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AutoScale 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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
AsleepMOTD string `usage:"MOTD to display when auto-scaled down servers are accessed; if empty, no status will be served"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RoutesConfig struct {
|
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"`
|
KubeNamespace string `usage:"The namespace to watch or blank for all, which is the default"`
|
||||||
InDocker bool `usage:"Use Docker service discovery"`
|
InDocker bool `usage:"Use Docker service discovery"`
|
||||||
InDockerSwarm bool `usage:"Use Docker Swarm 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"`
|
DockerSocket string `usage:"Path to Docker socket to use"`
|
||||||
DockerTimeout int `default:"0" usage:"Timeout configuration in seconds for the Docker integrations"`
|
DockerTimeout time.Duration `usage:"Timeout (as duration) for the Docker integrations"`
|
||||||
DockerRefreshInterval int `default:"15" usage:"Refresh interval in seconds 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"`
|
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
|
||||||
|
|||||||
+20
-19
@@ -29,12 +29,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type dockerWatcherConfig struct {
|
type dockerWatcherConfig struct {
|
||||||
autoScaleUp bool
|
autoScaleUp bool
|
||||||
autoScaleDown bool
|
autoScaleDown bool
|
||||||
socket string
|
socket string
|
||||||
timeoutSeconds int
|
timeout time.Duration
|
||||||
refreshIntervalSeconds int
|
refreshInterval time.Duration
|
||||||
apiVersion string
|
apiVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *dockerWatcherConfig) apiVersionOpt() client.Opt {
|
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{
|
return &dockerWatcherImpl{
|
||||||
config: dockerWatcherConfig{
|
config: dockerWatcherConfig{
|
||||||
socket: socket,
|
socket: socket,
|
||||||
timeoutSeconds: timeoutSeconds,
|
timeout: timeout,
|
||||||
refreshIntervalSeconds: refreshIntervalSeconds,
|
refreshInterval: refreshInterval,
|
||||||
autoScaleUp: autoScaleUp,
|
autoScaleUp: autoScaleUp,
|
||||||
autoScaleDown: autoScaleDown,
|
autoScaleDown: autoScaleDown,
|
||||||
apiVersion: dockerApiVersion,
|
apiVersion: dockerApiVersion,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,24 +225,25 @@ func (w *dockerWatcherImpl) monitorContainers(ctx context.Context) error {
|
|||||||
func (w *dockerWatcherImpl) Start(ctx context.Context) error {
|
func (w *dockerWatcherImpl) Start(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
timeout := time.Duration(w.config.timeoutSeconds) * time.Second
|
|
||||||
refreshInterval := time.Duration(w.config.refreshIntervalSeconds) * time.Second
|
|
||||||
|
|
||||||
opts := []client.Opt{
|
opts := []client.Opt{
|
||||||
client.WithHost(w.config.socket),
|
client.FromEnv,
|
||||||
client.WithTimeout(timeout),
|
client.WithTimeout(w.config.timeout),
|
||||||
client.WithHTTPHeaders(map[string]string{
|
client.WithHTTPHeaders(map[string]string{
|
||||||
"User-Agent": "mc-router ",
|
"User-Agent": "mc-router ",
|
||||||
}),
|
}),
|
||||||
w.config.apiVersionOpt(),
|
w.config.apiVersionOpt(),
|
||||||
}
|
}
|
||||||
|
if w.config.socket != "" {
|
||||||
|
opts = append(opts, client.WithHost(w.config.socket))
|
||||||
|
}
|
||||||
|
|
||||||
w.client, err = client.NewClientWithOpts(opts...)
|
w.client, err = client.NewClientWithOpts(opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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")
|
logrus.Trace("Performing initial listing of Docker containers")
|
||||||
initialContainers, err := w.listContainers(ctx)
|
initialContainers, err := w.listContainers(ctx)
|
||||||
|
|||||||
+9
-12
@@ -19,15 +19,15 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"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{
|
return &dockerSwarmWatcherImpl{
|
||||||
config: dockerWatcherConfig{
|
config: dockerWatcherConfig{
|
||||||
socket: socket,
|
socket: socket,
|
||||||
timeoutSeconds: timeoutSeconds,
|
timeout: timeout,
|
||||||
refreshIntervalSeconds: refreshIntervalSeconds,
|
refreshInterval: refreshInterval,
|
||||||
autoScaleUp: autoScaleUp,
|
autoScaleUp: autoScaleUp,
|
||||||
autoScaleDown: autoScaleDown,
|
autoScaleDown: autoScaleDown,
|
||||||
apiVersion: dockerApiVersion,
|
apiVersion: dockerApiVersion,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,12 +61,9 @@ func (w *dockerSwarmWatcherImpl) makeSleeperFunc(_ *routableService) SleeperFunc
|
|||||||
func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
|
func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
timeout := time.Duration(w.config.timeoutSeconds) * time.Second
|
|
||||||
refreshInterval := time.Duration(w.config.refreshIntervalSeconds) * time.Second
|
|
||||||
|
|
||||||
opts := []client.Opt{
|
opts := []client.Opt{
|
||||||
client.WithHost(w.config.socket),
|
client.WithHost(w.config.socket),
|
||||||
client.WithTimeout(timeout),
|
client.WithTimeout(w.config.timeout),
|
||||||
client.WithHTTPHeaders(map[string]string{
|
client.WithHTTPHeaders(map[string]string{
|
||||||
"User-Agent": "mc-router ",
|
"User-Agent": "mc-router ",
|
||||||
}),
|
}),
|
||||||
@@ -78,7 +75,7 @@ func (w *dockerSwarmWatcherImpl) Start(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ticker := time.NewTicker(refreshInterval)
|
ticker := time.NewTicker(w.config.refreshInterval)
|
||||||
serviceMap := map[string]*routableService{}
|
serviceMap := map[string]*routableService{}
|
||||||
|
|
||||||
logrus.Trace("Performing initial listing of Docker containers")
|
logrus.Trace("Performing initial listing of Docker containers")
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ func (ds *downScalerImpl) Begin(backendEndpoint string) {
|
|||||||
scaleDownCancel()
|
scaleDownCancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.WithField("backendEndpoint", backendEndpoint).Debug("Beginning scale down")
|
|
||||||
scaleDownContext, scaleDownContextCancellation := context.WithCancel(ds.parentContext)
|
scaleDownContext, scaleDownContextCancellation := context.WithCancel(ds.parentContext)
|
||||||
ds.contextCancellations[backendEndpoint] = scaleDownContextCancellation
|
ds.contextCancellations[backendEndpoint] = scaleDownContextCancellation
|
||||||
go ds.scaleDown(scaleDownContext, backendEndpoint)
|
go ds.scaleDown(scaleDownContext, backendEndpoint)
|
||||||
@@ -78,12 +77,18 @@ func (ds *downScalerImpl) Cancel(backendEndpoint string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ds *downScalerImpl) scaleDown(ctx context.Context, 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 {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-time.After(ds.delay):
|
case <-time.After(ds.delay):
|
||||||
sleepers := Routes.GetSleepers(backendEndpoint)
|
sleepers := Routes.GetSleepers(backendEndpoint)
|
||||||
|
logrus.WithField("backendEndpoint", backendEndpoint).
|
||||||
|
WithField("sleepers", len(sleepers)).
|
||||||
|
Debug("Found sleepers to use")
|
||||||
if len(sleepers) == 0 {
|
if len(sleepers) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -7,7 +7,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@@ -50,10 +49,7 @@ func NewServer(ctx context.Context, config *Config) (*Server, error) {
|
|||||||
metricsBuilder := NewMetricsBuilder(config.MetricsBackend, &config.MetricsBackendConfig)
|
metricsBuilder := NewMetricsBuilder(config.MetricsBackend, &config.MetricsBackendConfig)
|
||||||
|
|
||||||
downScalerEnabled := config.AutoScale.Down && (config.InKubeCluster || config.KubeConfig != "" || config.InDocker)
|
downScalerEnabled := config.AutoScale.Down && (config.InKubeCluster || config.KubeConfig != "" || config.InDocker)
|
||||||
downScalerDelay, err := time.ParseDuration(config.AutoScale.DownAfter)
|
downScalerDelay := config.AutoScale.DownAfter
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse auto-scale-down-after duration: %w", err)
|
|
||||||
}
|
|
||||||
// Only one instance should be created
|
// Only one instance should be created
|
||||||
DownScaler = NewDownScaler(ctx, downScalerEnabled, downScalerDelay)
|
DownScaler = NewDownScaler(ctx, downScalerEnabled, downScalerDelay)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user