Corrected docker/swarm discovery from previous refactoring (#446)

This commit is contained in:
Geoff Bourne
2025-08-23 13:39:01 -05:00
committed by GitHub
parent 4055f39b19
commit 35500a758b
9 changed files with 95 additions and 47 deletions
+35
View File
@@ -0,0 +1,35 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/go
{
"name": "Go",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "golang:1.24-bookworm",
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
// For in-docker discovery testing
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
25565
],
containerEnv: {
"GOROOT": "/usr/local/go"
},
// Configure tool-specific properties.
"customizations": {
"jetbrains": {
"backend": "IntelliJ",
"plugins": [
"org.jetbrains.plugins.go"
]
},
"vscode": {
"extensions": [
"golang.go"
]
}
}
}
+8
View File
@@ -0,0 +1,8 @@
## Developing Docker discovery on non-Linux
This works best with the included devcontaner setup, which includes attaching the host's docker socket to the dev container at `/var/run/docker.sock`.
On Windows, can create the devcontainer using:
![image.png](docs/create-dev-container.png)
+14 -15
View File
@@ -103,8 +103,6 @@ The [multi-architecture image published at Docker Hub](https://hub.docker.com/re
The diagram below shows how this `docker-compose.yml` configures two Minecraft server services named `vanilla` and `forge`, which also become the internal network aliases. _Notice those services don't need their ports exposed since the internal networking allows for the inter-container access._ The diagram below shows how this `docker-compose.yml` configures two Minecraft server services named `vanilla` and `forge`, which also become the internal network aliases. _Notice those services don't need their ports exposed since the internal networking allows for the inter-container access._
```yaml ```yaml
version: "3.8"
services: services:
vanilla: vanilla:
image: itzg/minecraft-server image: itzg/minecraft-server
@@ -141,20 +139,21 @@ To test out this example, add these two entries to my "hosts" file:
### Using Docker auto-discovery ### Using Docker auto-discovery
When running `mc-router` in a Docker environment you can pass the `--in-docker` or `--in-docker-swarm` When running `mc-router` in a Docker environment you can pass the `--in-docker` or `--in-docker-swarm` command-line argument or set the environment variables `IN_DOCKER` or `IN_DOCKER_SWARM` to "true". With that, it will poll the Docker API periodically to find all the running containers/services for Minecraft instances. To enable discovery, you have to set the `mc-router.host` label on the container.
command-line argument and it will poll the Docker API periodically to find all the running
containers/services for Minecraft instances. To enable discovery you have to set the `mc-router.host`
label on the container. These are the labels scanned:
- `mc-router.host`: Used to configure the hostname the Minecraft clients would use to When using in Docker, make sure to volume mount the Docker socket into the container, such as
connect to the server. The container/service endpoint will be used as the routed backend. You can
use more than one hostname by splitting it with a comma. ```yaml
- `mc-router.port`: This value must be set to the port the Minecraft server is listening on. volumes:
The default value is 25565. - /var/run/docker.sock:/var/run/docker.sock:ro
- `mc-router.default`: Set this to a truthy value to make this server the default backend. ```
Please note that `mc-router.host` is still required to be set.
- `mc-router.network`: Specify the network you are using for the router if multiple are These are the labels scanned:
present in the container/service. You can either use the network ID, it's full name or an alias.
- `mc-router.host`: Used to configure the hostname the Minecraft clients would use to connect to the server. The container/service endpoint will be used as the routed backend. You can use more than one hostname by splitting it with a comma.
- `mc-router.port`: This value must be set to the port the Minecraft server is listening on. The default value is 25565.
- `mc-router.default`: Set this to a truthy value to make this server the default backend. Please note that `mc-router.host` is still required to be set.
- `mc-router.network`: Specify the network you are using for the router if multiple are present in the container/service. You can either use the network ID, it's full name or an alias.
#### Example Docker deployment #### Example Docker deployment
+5 -1
View File
@@ -24,6 +24,7 @@ func showVersion() {
type CliConfig struct { type CliConfig struct {
Version bool `usage:"Output version and exit"` Version bool `usage:"Output version and exit"`
Debug bool `usage:"Enable debug logs"` Debug bool `usage:"Enable debug logs"`
Trace bool `usage:"Enable trace logs"`
ServerConfig server.Config `flatten:"true"` ServerConfig server.Config `flatten:"true"`
} }
@@ -40,7 +41,10 @@ func main() {
os.Exit(0) os.Exit(0)
} }
if cliConfig.Debug { if cliConfig.Trace {
logrus.SetLevel(logrus.TraceLevel)
logrus.Trace("Trace logs enabled")
} else if cliConfig.Debug {
logrus.SetLevel(logrus.DebugLevel) logrus.SetLevel(logrus.DebugLevel)
logrus.Debug("Debug logs enabled") logrus.Debug("Debug logs enabled")
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

+23
View File
@@ -0,0 +1,23 @@
services:
router:
image: itzg/mc-router
environment:
IN_DOCKER: true
ports:
- "25565:25565"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
vanilla:
image: itzg/minecraft-server
environment:
EULA: "TRUE"
labels:
mc-router.host: "vanilla.example.com"
paper:
image: itzg/minecraft-server
environment:
EULA: "TRUE"
TYPE: PAPER
labels:
mc-router.host: "paper.example.com"
+5 -13
View File
@@ -15,8 +15,7 @@ import (
) )
type IDockerWatcher interface { type IDockerWatcher interface {
Start(socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error Start(ctx context.Context, socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error
Stop()
} }
const ( const (
@@ -34,7 +33,6 @@ type dockerWatcherImpl struct {
autoScaleUp bool autoScaleUp bool
autoScaleDown bool autoScaleDown bool
client *client.Client client *client.Client
contextCancel context.CancelFunc
} }
func (w *dockerWatcherImpl) makeWakerFunc(_ *routableContainer) ScalerFunc { func (w *dockerWatcherImpl) makeWakerFunc(_ *routableContainer) ScalerFunc {
@@ -57,7 +55,7 @@ func (w *dockerWatcherImpl) makeSleeperFunc(_ *routableContainer) ScalerFunc {
} }
} }
func (w *dockerWatcherImpl) Start(socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error { func (w *dockerWatcherImpl) Start(ctx context.Context, socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error {
var err error var err error
w.autoScaleUp = autoScaleUp w.autoScaleUp = autoScaleUp
@@ -83,9 +81,7 @@ func (w *dockerWatcherImpl) Start(socket string, timeoutSeconds int, refreshInte
ticker := time.NewTicker(refreshInterval) ticker := time.NewTicker(refreshInterval)
containerMap := map[string]*routableContainer{} containerMap := map[string]*routableContainer{}
var ctx context.Context logrus.Trace("Performing initial listing of Docker containers")
ctx, w.contextCancel = context.WithCancel(context.Background())
initialContainers, err := w.listContainers(ctx) initialContainers, err := w.listContainers(ctx)
if err != nil { if err != nil {
return err return err
@@ -104,6 +100,7 @@ func (w *dockerWatcherImpl) Start(socket string, timeoutSeconds int, refreshInte
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
logrus.Trace("Listing Docker containers")
containers, err := w.listContainers(ctx) containers, err := w.listContainers(ctx)
if err != nil { if err != nil {
logrus.WithError(err).Error("Docker failed to list containers") logrus.WithError(err).Error("Docker failed to list containers")
@@ -145,6 +142,7 @@ func (w *dockerWatcherImpl) Start(socket string, timeoutSeconds int, refreshInte
} }
case <-ctx.Done(): case <-ctx.Done():
logrus.Debug("Stopping Docker monitoring")
ticker.Stop() ticker.Stop()
return return
} }
@@ -303,12 +301,6 @@ func (w *dockerWatcherImpl) parseContainerData(container *dockertypes.Container)
return return
} }
func (w *dockerWatcherImpl) Stop() {
if w.contextCancel != nil {
w.contextCancel()
}
}
type routableContainer struct { type routableContainer struct {
externalContainerName string externalContainerName string
containerEndpoint string containerEndpoint string
+3 -12
View File
@@ -26,7 +26,6 @@ type dockerSwarmWatcherImpl struct {
autoScaleUp bool autoScaleUp bool
autoScaleDown bool autoScaleDown bool
client *client.Client client *client.Client
contextCancel context.CancelFunc
} }
func (w *dockerSwarmWatcherImpl) makeWakerFunc(_ *routableService) ScalerFunc { func (w *dockerSwarmWatcherImpl) makeWakerFunc(_ *routableService) ScalerFunc {
@@ -49,7 +48,7 @@ func (w *dockerSwarmWatcherImpl) makeSleeperFunc(_ *routableService) ScalerFunc
} }
} }
func (w *dockerSwarmWatcherImpl) Start(socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error { func (w *dockerSwarmWatcherImpl) Start(ctx context.Context, socket string, timeoutSeconds int, refreshIntervalSeconds int, autoScaleUp bool, autoScaleDown bool) error {
var err error var err error
w.autoScaleUp = autoScaleUp w.autoScaleUp = autoScaleUp
@@ -75,9 +74,7 @@ func (w *dockerSwarmWatcherImpl) Start(socket string, timeoutSeconds int, refres
ticker := time.NewTicker(refreshInterval) ticker := time.NewTicker(refreshInterval)
serviceMap := map[string]*routableService{} serviceMap := map[string]*routableService{}
var ctx context.Context logrus.Trace("Performing initial listing of Docker containers")
ctx, w.contextCancel = context.WithCancel(context.Background())
initialServices, err := w.listServices(ctx) initialServices, err := w.listServices(ctx)
if err != nil { if err != nil {
return err return err
@@ -99,7 +96,7 @@ func (w *dockerSwarmWatcherImpl) Start(socket string, timeoutSeconds int, refres
services, err := w.listServices(ctx) services, err := w.listServices(ctx)
if err != nil { if err != nil {
logrus.WithError(err).Error("Docker failed to list services") logrus.WithError(err).Error("Docker failed to list services")
return continue
} }
visited := map[string]struct{}{} visited := map[string]struct{}{}
@@ -332,9 +329,3 @@ func (w *dockerSwarmWatcherImpl) parseServiceData(service *swarm.Service, networ
ok = true ok = true
return return
} }
func (w *dockerSwarmWatcherImpl) Stop() {
if w.contextCancel != nil {
w.contextCancel()
}
}
+2 -6
View File
@@ -142,21 +142,17 @@ 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(config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down) err = DockerWatcher.Start(ctx, config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down)
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)
} else {
defer DockerWatcher.Stop()
} }
} }
// TODO convert to RouteFinder // TODO convert to RouteFinder
if config.InDockerSwarm { if config.InDockerSwarm {
err = DockerSwarmWatcher.Start(config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down) err = DockerSwarmWatcher.Start(ctx, config.DockerSocket, config.DockerTimeout, config.DockerRefreshInterval, config.AutoScale.Up, config.AutoScale.Down)
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)
} else {
defer DockerSwarmWatcher.Stop()
} }
} }