Skip to content

Commit

Permalink
Add support for a default backend service
Browse files Browse the repository at this point in the history
  • Loading branch information
itzg committed Apr 20, 2019
1 parent d76e544 commit 44a67dd
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 33 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/vendor/

/.idea/
/*.iml
/*.iml
/mc-router.exe
/mc-router
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,24 @@ Flags:
"backend": "HOST:PORT"
}
```

* `POST /defaultRoute`
Registers a default route to the given backend. JSON body is structured as:
```json
{
"backend": "HOST:PORT"
}
```
* `DELETE /routes/{serverAddress}`
Deletes an existing route for the given `serverAddress`

## Using kubernetes service auto-discovery

When running `mc-router` as a kubernetes pod and you pass the `--in-kube-cluster` command-line argument, then
it will automatically watch for any services annotated with `mc-router.itzg.me/externalServerName`. The value
of the annotation will be registered as the external hostname Minecraft clients would used to connect to the
routed service. The service's clusterIP and target port are used as the routed backend.
it will automatically watch for any services annotated with
- `mc-router.itzg.me/externalServerName` : The value of the annotation will be registered as the external hostname Minecraft clients would used to connect to the
routed service. The service's clusterIP and target port are used as the routed backend.
- `mc-router.itzg.me/defaultServer` : The service's clusterIP and target port are used as the default if
no other `externalServiceName` annotations applies.

For example, start `mc-router`'s container spec with

Expand Down
94 changes: 94 additions & 0 deletions docs/k8s-mc-with-default.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
apiVersion: v1
kind: Service
metadata:
name: mc-stable
annotations:
"mc-router.itzg.me/defaultServer": "mc.your.domain"
spec:
ports:
- port: 25565
selector:
run: mc-stable
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: mc-stable
name: mc-stable
spec:
selector:
matchLabels:
run: mc-stable
template:
metadata:
labels:
run: mc-stable
spec:
securityContext:
runAsUser: 1000
fsGroup: 1000
containers:
- image: itzg/minecraft-server
name: mc-stable
env:
- name: EULA
value: "TRUE"
ports:
- containerPort: 25565
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: mc-stable
---
apiVersion: v1
kind: Service
metadata:
name: mc-snapshot
annotations:
"mc-router.itzg.me/externalServerName": "snapshot.your.domain"
spec:
ports:
- port: 25565
selector:
run: mc-snapshot
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: mc-snapshot
name: mc-snapshot
spec:
selector:
matchLabels:
run: mc-snapshot
template:
metadata:
labels:
run: mc-snapshot
spec:
securityContext:
runAsUser: 1000
fsGroup: 1000
containers:
- image: itzg/minecraft-server
name: mc-snapshot
env:
- name: EULA
value: "TRUE"
- name: VERSION
value: "SNAPSHOT"
ports:
- containerPort: 25565
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: mc-snapshot
61 changes: 42 additions & 19 deletions server/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ import (
"net"
)

const (
AnnotationExternalServerName = "mc-router.itzg.me/externalServerName"
AnnotationDefaultServer = "mc-router.itzg.me/defaultServer"
)

type IK8sWatcher interface {
StartWithConfig(kubeConfigFile string) error
StartInCluster() error
Expand Down Expand Up @@ -65,15 +70,23 @@ func (w *k8sWatcherImpl) startWithLoadedConfig(config *rest.Config) error {
if routableService != nil {
logrus.WithField("routableService", routableService).Debug("ADD")

Routes.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint)
if routableService.externalServiceName != "" {
Routes.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint)
} else {
Routes.SetDefaultRoute(routableService.containerEndpoint)
}
}
},
DeleteFunc: func(obj interface{}) {
routableService := extractRoutableService(obj)
if routableService != nil {
logrus.WithField("routableService", routableService).Debug("DELETE")

Routes.DeleteMapping(routableService.externalServiceName)
if routableService.externalServiceName != "" {
Routes.DeleteMapping(routableService.externalServiceName)
} else {
Routes.SetDefaultRoute("")
}
}
},
UpdateFunc: func(oldObj, newObj interface{}) {
Expand All @@ -85,8 +98,12 @@ func (w *k8sWatcherImpl) startWithLoadedConfig(config *rest.Config) error {
"new": newRoutableService,
}).Debug("UPDATE")

Routes.DeleteMapping(oldRoutableService.externalServiceName)
Routes.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint)
if oldRoutableService.externalServiceName != "" && newRoutableService.externalServiceName != "" {
Routes.DeleteMapping(oldRoutableService.externalServiceName)
Routes.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint)
} else {
Routes.SetDefaultRoute(newRoutableService.containerEndpoint)
}
}
},
},
Expand Down Expand Up @@ -116,22 +133,28 @@ func extractRoutableService(obj interface{}) *routableService {
return nil
}

if externalServiceName, exists := service.Annotations["mc-router.itzg.me/externalServerName"]; exists {
clusterIp := service.Spec.ClusterIP
port := "25565"
for _, p := range service.Spec.Ports {
if p.Port == 25565 {
if p.TargetPort.String() != "" {
port = p.TargetPort.String()
}
}
}
rs := &routableService{
externalServiceName: externalServiceName,
containerEndpoint: net.JoinHostPort(clusterIp, port),
}
return rs
if externalServiceName, exists := service.Annotations[AnnotationExternalServerName]; exists {
return buildDetails(service, externalServiceName)
} else if _, exists := service.Annotations[AnnotationDefaultServer]; exists {
return buildDetails(service, "")
}

return nil
}

func buildDetails(service *v1.Service, externalServiceName string) *routableService {
clusterIp := service.Spec.ClusterIP
port := "25565"
for _, p := range service.Spec.Ports {
if p.Port == 25565 {
if p.TargetPort.String() != "" {
port = p.TargetPort.String()
}
}
}
rs := &routableService{
externalServiceName: externalServiceName,
containerEndpoint: net.JoinHostPort(clusterIp, port),
}
return rs
}
55 changes: 46 additions & 9 deletions server/routes.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package server

import (
"sync"
"net/http"
"encoding/json"
"github.com/sirupsen/logrus"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"net/http"
"sync"
)

func init() {
Expand All @@ -15,6 +15,9 @@ func init() {
apiRoutes.Path("/routes").Methods("POST").
Headers("Content-Type", "application/json").
HandlerFunc(routesCreateHandler)
apiRoutes.Path("/defaultRoute").Methods("POST").
Headers("Content-Type", "application/json").
HandlerFunc(routesSetDefault)
apiRoutes.Path("/routes/{serverAddress}").Methods("DELETE").HandlerFunc(routesDeleteHandler)
}

Expand Down Expand Up @@ -43,7 +46,7 @@ func routesDeleteHandler(writer http.ResponseWriter, request *http.Request) {
func routesCreateHandler(writer http.ResponseWriter, request *http.Request) {
var definition = struct {
ServerAddress string
Backend string
Backend string
}{}

defer request.Body.Close()
Expand All @@ -60,6 +63,25 @@ func routesCreateHandler(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusCreated)
}

func routesSetDefault(writer http.ResponseWriter, request *http.Request) {
var body = struct {
Backend string
}{}

defer request.Body.Close()

decoder := json.NewDecoder(request.Body)
err := decoder.Decode(&body)
if err != nil {
logrus.WithError(err).Error("Unable to parse request")
writer.WriteHeader(http.StatusBadRequest)
return
}

Routes.SetDefaultRoute(body.Backend)
writer.WriteHeader(http.StatusOK)
}

type IRoutes interface {
RegisterAll(mappings map[string]string)
// FindBackendForServerAddress returns the host:port for the external server address, if registered.
Expand All @@ -68,6 +90,7 @@ type IRoutes interface {
GetMappings() map[string]string
DeleteMapping(serverAddress string) bool
CreateMapping(serverAddress string, backend string)
SetDefaultRoute(backend string)
}

var Routes IRoutes = &routesImpl{}
Expand All @@ -81,17 +104,31 @@ func (r *routesImpl) RegisterAll(mappings map[string]string) {

type routesImpl struct {
sync.RWMutex
mappings map[string]string
mappings map[string]string
defaultRoute string
}

func (r *routesImpl) SetDefaultRoute(backend string) {
r.defaultRoute = backend

logrus.WithFields(logrus.Fields{
"backend": backend,
}).Info("Using default route")
}

func (r *routesImpl) FindBackendForServerAddress(serverAddress string) string {
r.RLock()
defer r.RUnlock()

if r.mappings == nil {
return ""
return r.defaultRoute
} else {
return r.mappings[serverAddress]

if route, exists := r.mappings[serverAddress]; exists {
return route
} else {
return r.defaultRoute
}
}
}

Expand Down Expand Up @@ -125,7 +162,7 @@ func (r *routesImpl) CreateMapping(serverAddress string, backend string) {

logrus.WithFields(logrus.Fields{
"serverAddress": serverAddress,
"backend": backend,
"backend": backend,
}).Info("Creating route")
r.mappings[serverAddress] = backend
}
}

0 comments on commit 44a67dd

Please sign in to comment.