mirror of
https://github.com/itzg/mc-router.git
synced 2024-11-21 11:25:41 +01:00
Add support for a default backend service
This commit is contained in:
parent
d76e5442da
commit
44a67dd359
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +1,6 @@
|
||||
/vendor/
|
||||
|
||||
/.idea/
|
||||
/*.iml
|
||||
/*.iml
|
||||
/mc-router.exe
|
||||
/mc-router
|
||||
|
16
README.md
16
README.md
@ -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
|
||||
|
||||
|
94
docs/k8s-mc-with-default.yaml
Normal file
94
docs/k8s-mc-with-default.yaml
Normal file
@ -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
|
@ -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
|
||||
@ -65,7 +70,11 @@ 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{}) {
|
||||
@ -73,7 +82,11 @@ func (w *k8sWatcherImpl) startWithLoadedConfig(config *rest.Config) error {
|
||||
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{}) {
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -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
|
||||
}
|
||||
|
@ -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() {
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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()
|
||||
@ -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.
|
||||
@ -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{}
|
||||
@ -81,7 +104,16 @@ 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 {
|
||||
@ -89,9 +121,14 @@ func (r *routesImpl) FindBackendForServerAddress(serverAddress string) string {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user