Skip to content

Commit 44a67dd

Browse files
committed
Add support for a default backend service
1 parent d76e544 commit 44a67dd

File tree

5 files changed

+197
-33
lines changed

5 files changed

+197
-33
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
/vendor/
22

33
/.idea/
4-
/*.iml
4+
/*.iml
5+
/mc-router.exe
6+
/mc-router

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,24 @@ Flags:
2525
"backend": "HOST:PORT"
2626
}
2727
```
28-
28+
* `POST /defaultRoute`
29+
Registers a default route to the given backend. JSON body is structured as:
30+
```json
31+
{
32+
"backend": "HOST:PORT"
33+
}
34+
```
2935
* `DELETE /routes/{serverAddress}`
3036
Deletes an existing route for the given `serverAddress`
3137

3238
## Using kubernetes service auto-discovery
3339

3440
When running `mc-router` as a kubernetes pod and you pass the `--in-kube-cluster` command-line argument, then
35-
it will automatically watch for any services annotated with `mc-router.itzg.me/externalServerName`. The value
36-
of the annotation will be registered as the external hostname Minecraft clients would used to connect to the
37-
routed service. The service's clusterIP and target port are used as the routed backend.
41+
it will automatically watch for any services annotated with
42+
- `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
43+
routed service. The service's clusterIP and target port are used as the routed backend.
44+
- `mc-router.itzg.me/defaultServer` : The service's clusterIP and target port are used as the default if
45+
no other `externalServiceName` annotations applies.
3846

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

docs/k8s-mc-with-default.yaml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
---
2+
apiVersion: v1
3+
kind: Service
4+
metadata:
5+
name: mc-stable
6+
annotations:
7+
"mc-router.itzg.me/defaultServer": "mc.your.domain"
8+
spec:
9+
ports:
10+
- port: 25565
11+
selector:
12+
run: mc-stable
13+
---
14+
apiVersion: apps/v1
15+
kind: Deployment
16+
metadata:
17+
labels:
18+
run: mc-stable
19+
name: mc-stable
20+
spec:
21+
selector:
22+
matchLabels:
23+
run: mc-stable
24+
template:
25+
metadata:
26+
labels:
27+
run: mc-stable
28+
spec:
29+
securityContext:
30+
runAsUser: 1000
31+
fsGroup: 1000
32+
containers:
33+
- image: itzg/minecraft-server
34+
name: mc-stable
35+
env:
36+
- name: EULA
37+
value: "TRUE"
38+
ports:
39+
- containerPort: 25565
40+
volumeMounts:
41+
- name: data
42+
mountPath: /data
43+
volumes:
44+
- name: data
45+
persistentVolumeClaim:
46+
claimName: mc-stable
47+
---
48+
apiVersion: v1
49+
kind: Service
50+
metadata:
51+
name: mc-snapshot
52+
annotations:
53+
"mc-router.itzg.me/externalServerName": "snapshot.your.domain"
54+
spec:
55+
ports:
56+
- port: 25565
57+
selector:
58+
run: mc-snapshot
59+
---
60+
apiVersion: apps/v1
61+
kind: Deployment
62+
metadata:
63+
labels:
64+
run: mc-snapshot
65+
name: mc-snapshot
66+
spec:
67+
selector:
68+
matchLabels:
69+
run: mc-snapshot
70+
template:
71+
metadata:
72+
labels:
73+
run: mc-snapshot
74+
spec:
75+
securityContext:
76+
runAsUser: 1000
77+
fsGroup: 1000
78+
containers:
79+
- image: itzg/minecraft-server
80+
name: mc-snapshot
81+
env:
82+
- name: EULA
83+
value: "TRUE"
84+
- name: VERSION
85+
value: "SNAPSHOT"
86+
ports:
87+
- containerPort: 25565
88+
volumeMounts:
89+
- name: data
90+
mountPath: /data
91+
volumes:
92+
- name: data
93+
persistentVolumeClaim:
94+
claimName: mc-snapshot

server/k8s.go

Lines changed: 42 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ import (
1212
"net"
1313
)
1414

15+
const (
16+
AnnotationExternalServerName = "mc-router.itzg.me/externalServerName"
17+
AnnotationDefaultServer = "mc-router.itzg.me/defaultServer"
18+
)
19+
1520
type IK8sWatcher interface {
1621
StartWithConfig(kubeConfigFile string) error
1722
StartInCluster() error
@@ -65,15 +70,23 @@ func (w *k8sWatcherImpl) startWithLoadedConfig(config *rest.Config) error {
6570
if routableService != nil {
6671
logrus.WithField("routableService", routableService).Debug("ADD")
6772

68-
Routes.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint)
73+
if routableService.externalServiceName != "" {
74+
Routes.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint)
75+
} else {
76+
Routes.SetDefaultRoute(routableService.containerEndpoint)
77+
}
6978
}
7079
},
7180
DeleteFunc: func(obj interface{}) {
7281
routableService := extractRoutableService(obj)
7382
if routableService != nil {
7483
logrus.WithField("routableService", routableService).Debug("DELETE")
7584

76-
Routes.DeleteMapping(routableService.externalServiceName)
85+
if routableService.externalServiceName != "" {
86+
Routes.DeleteMapping(routableService.externalServiceName)
87+
} else {
88+
Routes.SetDefaultRoute("")
89+
}
7790
}
7891
},
7992
UpdateFunc: func(oldObj, newObj interface{}) {
@@ -85,8 +98,12 @@ func (w *k8sWatcherImpl) startWithLoadedConfig(config *rest.Config) error {
8598
"new": newRoutableService,
8699
}).Debug("UPDATE")
87100

88-
Routes.DeleteMapping(oldRoutableService.externalServiceName)
89-
Routes.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint)
101+
if oldRoutableService.externalServiceName != "" && newRoutableService.externalServiceName != "" {
102+
Routes.DeleteMapping(oldRoutableService.externalServiceName)
103+
Routes.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint)
104+
} else {
105+
Routes.SetDefaultRoute(newRoutableService.containerEndpoint)
106+
}
90107
}
91108
},
92109
},
@@ -116,22 +133,28 @@ func extractRoutableService(obj interface{}) *routableService {
116133
return nil
117134
}
118135

119-
if externalServiceName, exists := service.Annotations["mc-router.itzg.me/externalServerName"]; exists {
120-
clusterIp := service.Spec.ClusterIP
121-
port := "25565"
122-
for _, p := range service.Spec.Ports {
123-
if p.Port == 25565 {
124-
if p.TargetPort.String() != "" {
125-
port = p.TargetPort.String()
126-
}
127-
}
128-
}
129-
rs := &routableService{
130-
externalServiceName: externalServiceName,
131-
containerEndpoint: net.JoinHostPort(clusterIp, port),
132-
}
133-
return rs
136+
if externalServiceName, exists := service.Annotations[AnnotationExternalServerName]; exists {
137+
return buildDetails(service, externalServiceName)
138+
} else if _, exists := service.Annotations[AnnotationDefaultServer]; exists {
139+
return buildDetails(service, "")
134140
}
135141

136142
return nil
137143
}
144+
145+
func buildDetails(service *v1.Service, externalServiceName string) *routableService {
146+
clusterIp := service.Spec.ClusterIP
147+
port := "25565"
148+
for _, p := range service.Spec.Ports {
149+
if p.Port == 25565 {
150+
if p.TargetPort.String() != "" {
151+
port = p.TargetPort.String()
152+
}
153+
}
154+
}
155+
rs := &routableService{
156+
externalServiceName: externalServiceName,
157+
containerEndpoint: net.JoinHostPort(clusterIp, port),
158+
}
159+
return rs
160+
}

server/routes.go

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package server
22

33
import (
4-
"sync"
5-
"net/http"
64
"encoding/json"
7-
"github.com/sirupsen/logrus"
85
"github.com/gorilla/mux"
6+
"github.com/sirupsen/logrus"
7+
"net/http"
8+
"sync"
99
)
1010

1111
func init() {
@@ -15,6 +15,9 @@ func init() {
1515
apiRoutes.Path("/routes").Methods("POST").
1616
Headers("Content-Type", "application/json").
1717
HandlerFunc(routesCreateHandler)
18+
apiRoutes.Path("/defaultRoute").Methods("POST").
19+
Headers("Content-Type", "application/json").
20+
HandlerFunc(routesSetDefault)
1821
apiRoutes.Path("/routes/{serverAddress}").Methods("DELETE").HandlerFunc(routesDeleteHandler)
1922
}
2023

@@ -43,7 +46,7 @@ func routesDeleteHandler(writer http.ResponseWriter, request *http.Request) {
4346
func routesCreateHandler(writer http.ResponseWriter, request *http.Request) {
4447
var definition = struct {
4548
ServerAddress string
46-
Backend string
49+
Backend string
4750
}{}
4851

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

66+
func routesSetDefault(writer http.ResponseWriter, request *http.Request) {
67+
var body = struct {
68+
Backend string
69+
}{}
70+
71+
defer request.Body.Close()
72+
73+
decoder := json.NewDecoder(request.Body)
74+
err := decoder.Decode(&body)
75+
if err != nil {
76+
logrus.WithError(err).Error("Unable to parse request")
77+
writer.WriteHeader(http.StatusBadRequest)
78+
return
79+
}
80+
81+
Routes.SetDefaultRoute(body.Backend)
82+
writer.WriteHeader(http.StatusOK)
83+
}
84+
6385
type IRoutes interface {
6486
RegisterAll(mappings map[string]string)
6587
// FindBackendForServerAddress returns the host:port for the external server address, if registered.
@@ -68,6 +90,7 @@ type IRoutes interface {
6890
GetMappings() map[string]string
6991
DeleteMapping(serverAddress string) bool
7092
CreateMapping(serverAddress string, backend string)
93+
SetDefaultRoute(backend string)
7194
}
7295

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

82105
type routesImpl struct {
83106
sync.RWMutex
84-
mappings map[string]string
107+
mappings map[string]string
108+
defaultRoute string
109+
}
110+
111+
func (r *routesImpl) SetDefaultRoute(backend string) {
112+
r.defaultRoute = backend
113+
114+
logrus.WithFields(logrus.Fields{
115+
"backend": backend,
116+
}).Info("Using default route")
85117
}
86118

87119
func (r *routesImpl) FindBackendForServerAddress(serverAddress string) string {
88120
r.RLock()
89121
defer r.RUnlock()
90122

91123
if r.mappings == nil {
92-
return ""
124+
return r.defaultRoute
93125
} else {
94-
return r.mappings[serverAddress]
126+
127+
if route, exists := r.mappings[serverAddress]; exists {
128+
return route
129+
} else {
130+
return r.defaultRoute
131+
}
95132
}
96133
}
97134

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

126163
logrus.WithFields(logrus.Fields{
127164
"serverAddress": serverAddress,
128-
"backend": backend,
165+
"backend": backend,
129166
}).Info("Creating route")
130167
r.mappings[serverAddress] = backend
131-
}
168+
}

0 commit comments

Comments
 (0)