From 2befd24b4a29197685806c3a67c6bc559224657a Mon Sep 17 00:00:00 2001 From: Geoff Bourne Date: Sat, 26 May 2018 12:31:40 -0500 Subject: [PATCH] Auto-register routes based on kubernetes service annotations --- .circleci/config.yml | 12 ++- Gopkg.lock | 63 ----------- Gopkg.toml | 38 ------- cmd/mc-router/main.go | 32 +++--- glide.lock | 238 ++++++++++++++++++++++++++++++++++++++++++ glide.yaml | 16 +++ server/k8s.go | 122 ++++++++++++++++++++++ 7 files changed, 404 insertions(+), 117 deletions(-) delete mode 100644 Gopkg.lock delete mode 100644 Gopkg.toml create mode 100644 glide.lock create mode 100644 glide.yaml create mode 100644 server/k8s.go diff --git a/.circleci/config.yml b/.circleci/config.yml index be3130e..9cfc389 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -24,14 +24,16 @@ jobs: - restore_cache: keys: - - cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gopkg.lock" }} + - cache-{{ arch }}-{{ .Branch }}-{{ checksum "glide.lock" }} - cache-{{ arch }}-{{ .Branch }} - cache - - run: make install-dep + - run: + name: install dependencies + command: glide install - save_cache: - key: cache-{{ arch }}-{{ .Branch }}-{{ checksum "Gopkg.lock" }} + key: cache-{{ arch }}-{{ .Branch }}-{{ checksum "glide.lock" }} paths: - vendor @@ -42,7 +44,9 @@ jobs: steps: - checkout - - run: make install-dep + - run: + name: install dependencies + command: glide install - setup_remote_docker - run: echo $DOCKER_PASSWORD | docker login -u $DOCKER_USER --password-stdin diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index b448c0e..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,63 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/alecthomas/kingpin" - packages = ["."] - revision = "947dcec5ba9c011838740e680966fd7087a71d0d" - version = "v2.2.6" - -[[projects]] - branch = "master" - name = "github.com/alecthomas/template" - packages = [ - ".", - "parse" - ] - revision = "a0175ee3bccc567396460bf5acd36800cb10c49c" - -[[projects]] - branch = "master" - name = "github.com/alecthomas/units" - packages = ["."] - revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" - -[[projects]] - name = "github.com/gorilla/context" - packages = ["."] - revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" - version = "v1.1" - -[[projects]] - name = "github.com/gorilla/mux" - packages = ["."] - revision = "53c1911da2b537f792e7cafcb446b05ffe33b996" - version = "v1.6.1" - -[[projects]] - name = "github.com/sirupsen/logrus" - packages = ["."] - revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" - version = "v1.0.5" - -[[projects]] - branch = "master" - name = "golang.org/x/crypto" - packages = ["ssh/terminal"] - revision = "4ec37c66abab2c7e02ae775328b2ff001c3f025a" - -[[projects]] - branch = "master" - name = "golang.org/x/sys" - packages = [ - "unix", - "windows" - ] - revision = "6f686a352de66814cdd080d970febae7767857a3" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "8d17b95931fea7bbf18743367ccc152c392add293fe7945ca9ce941ca1482b4f" - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index c5a54ee..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,38 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[prune] - go-tests = true - unused-packages = true - -[[constraint]] - name = "github.com/alecthomas/kingpin" - version = "2.2.6" - -[[constraint]] - name = "github.com/sirupsen/logrus" - version = "1.0.5" diff --git a/cmd/mc-router/main.go b/cmd/mc-router/main.go index 8ce0f81..cf4de2b 100644 --- a/cmd/mc-router/main.go +++ b/cmd/mc-router/main.go @@ -1,35 +1,36 @@ package main import ( - "net" - "github.com/itzg/mc-router/server" - "github.com/alecthomas/kingpin" - "strconv" - "github.com/sirupsen/logrus" "context" + "fmt" + "github.com/alecthomas/kingpin" + "github.com/itzg/mc-router/server" + "github.com/sirupsen/logrus" + "net" "os" "os/signal" - "fmt" + "strconv" ) var ( port = kingpin.Flag("port", "The port bound to listen for Minecraft client connections"). Default("25565").Int() apiBinding = kingpin.Flag("api-binding", "The host:port bound for servicing API requests"). - String() + String() mappings = kingpin.Flag("mapping", "Mapping of external hostname to internal server host:port"). - StringMap() + StringMap() versionFlag = kingpin.Flag("version", "Output version and exit"). - Bool() + Bool() + kubeConfigFile = kingpin.Flag("kube-config", "The path to a kubernetes configuration file").String() ) var ( version = "dev" - commit = "none" - date = "unknown" + commit = "none" + date = "unknown" ) -func showVersion() { +func showVersion() { fmt.Printf("%v, commit %v, built at %v", version, commit, date) } @@ -54,6 +55,13 @@ func main() { server.StartApiServer(*apiBinding) } + err := server.K8sWatcher.Start(*kubeConfigFile) + if err != nil { + logrus.WithError(err).Warn("Skipping kubernetes integration") + } else { + defer server.K8sWatcher.Stop() + } + <-c logrus.Info("Stopping") cancel() diff --git a/glide.lock b/glide.lock new file mode 100644 index 0000000..5beb2c8 --- /dev/null +++ b/glide.lock @@ -0,0 +1,238 @@ +hash: d0042501db8c547ec44ff6828ff8b0c7d08561d95311f4c62cf8014e6a02e97e +updated: 2018-05-26T11:09:13.8257578-05:00 +imports: +- name: github.com/alecthomas/kingpin + version: 947dcec5ba9c011838740e680966fd7087a71d0d +- name: github.com/alecthomas/template + version: a0175ee3bccc567396460bf5acd36800cb10c49c + subpackages: + - parse +- name: github.com/alecthomas/units + version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a +- name: github.com/davecgh/go-spew + version: 782f4967f2dc4564575ca782fe2d04090b5faca8 + subpackages: + - spew +- name: github.com/ghodss/yaml + version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee +- name: github.com/gogo/protobuf + version: c0656edd0d9eab7c66d1eb0c568f9039345796f7 + subpackages: + - proto + - sortkeys +- name: github.com/golang/glog + version: 44145f04b68cf362d9c4df2182967c2275eaefed +- name: github.com/golang/protobuf + version: 1643683e1b54a9e88ad26d98f81400c8c9d9f4f9 + subpackages: + - proto + - ptypes + - ptypes/any + - ptypes/duration + - ptypes/timestamp +- name: github.com/google/gofuzz + version: 44d81051d367757e1c7c6a5a86423ece9afcf63c +- name: github.com/googleapis/gnostic + version: 0c5108395e2debce0d731cf0287ddf7242066aba + subpackages: + - OpenAPIv2 + - compiler + - extensions +- name: github.com/gorilla/context + version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 +- name: github.com/gorilla/mux + version: e3702bed27f0d39777b0b37b664b6280e8ef8fbf +- name: github.com/hashicorp/golang-lru + version: a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4 + subpackages: + - simplelru +- name: github.com/howeyc/gopass + version: bf9dde6d0d2c004a008c27aaee91170c786f6db8 +- name: github.com/imdario/mergo + version: 6633656539c1639d9d78127b7d47c622b5d7b6dc +- name: github.com/json-iterator/go + version: 13f86432b882000a51c6e610c620974462691a97 +- name: github.com/pkg/errors + version: 645ef00459ed84a119197bfb8d8205042c6df63d +- name: github.com/sirupsen/logrus + version: c155da19408a8799da419ed3eeb0cb5db0ad5dbc +- name: github.com/spf13/pflag + version: 4c012f6dcd9546820e378d0bdda4d8fc772cdfea +- name: golang.org/x/crypto + version: a3beeb748656e13e54256fd2cde19e058f41f60f + subpackages: + - ssh/terminal +- name: golang.org/x/net + version: 1c05540f6879653db88113bc4a2b70aec4bd491f + subpackages: + - context + - context/ctxhttp + - http2 + - http2/hpack + - idna + - lex/httplex +- name: golang.org/x/sys + version: 95c6576299259db960f6c5b9b69ea52422860fce + subpackages: + - unix + - windows +- name: golang.org/x/text + version: b19bf474d317b857955b12035d2c5acb57ce8b01 + subpackages: + - secure/bidirule + - transform + - unicode/bidi + - unicode/norm +- name: golang.org/x/time + version: f51c12702a4d776e4c1fa9b0fabab841babae631 + subpackages: + - rate +- name: gopkg.in/inf.v0 + version: 3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4 +- name: gopkg.in/yaml.v2 + version: 670d4cfef0544295bc27a114dbac37980d83185a +- name: k8s.io/api + version: 73d903622b7391f3312dcbac6483fed484e185f8 + subpackages: + - admissionregistration/v1alpha1 + - admissionregistration/v1beta1 + - apps/v1 + - apps/v1beta1 + - apps/v1beta2 + - authentication/v1 + - authentication/v1beta1 + - authorization/v1 + - authorization/v1beta1 + - autoscaling/v1 + - autoscaling/v2beta1 + - batch/v1 + - batch/v1beta1 + - batch/v2alpha1 + - certificates/v1beta1 + - core/v1 + - events/v1beta1 + - extensions/v1beta1 + - imagepolicy/v1alpha1 + - networking/v1 + - policy/v1beta1 + - rbac/v1 + - rbac/v1alpha1 + - rbac/v1beta1 + - scheduling/v1alpha1 + - settings/v1alpha1 + - storage/v1 + - storage/v1alpha1 + - storage/v1beta1 +- name: k8s.io/apimachinery + version: 302974c03f7e50f16561ba237db776ab93594ef6 + subpackages: + - pkg/api/equality + - pkg/api/errors + - pkg/api/meta + - pkg/api/resource + - pkg/api/testing + - pkg/api/testing/fuzzer + - pkg/api/testing/roundtrip + - pkg/apimachinery + - pkg/apimachinery/announced + - pkg/apimachinery/registered + - pkg/apis/meta/fuzzer + - pkg/apis/meta/internalversion + - pkg/apis/meta/v1 + - pkg/apis/meta/v1/unstructured + - pkg/apis/meta/v1beta1 + - pkg/conversion + - pkg/conversion/queryparams + - pkg/fields + - pkg/labels + - pkg/runtime + - pkg/runtime/schema + - pkg/runtime/serializer + - pkg/runtime/serializer/json + - pkg/runtime/serializer/protobuf + - pkg/runtime/serializer/recognizer + - pkg/runtime/serializer/streaming + - pkg/runtime/serializer/versioning + - pkg/selection + - pkg/types + - pkg/util/cache + - pkg/util/clock + - pkg/util/diff + - pkg/util/errors + - pkg/util/framer + - pkg/util/httpstream + - pkg/util/httpstream/spdy + - pkg/util/intstr + - pkg/util/json + - pkg/util/mergepatch + - pkg/util/net + - pkg/util/remotecommand + - pkg/util/runtime + - pkg/util/sets + - pkg/util/strategicpatch + - pkg/util/validation + - pkg/util/validation/field + - pkg/util/wait + - pkg/util/yaml + - pkg/version + - pkg/watch + - third_party/forked/golang/json + - third_party/forked/golang/netutil + - third_party/forked/golang/reflect +- name: k8s.io/client-go + version: 23781f4d6632d88e869066eaebb743857aa1ef9b + subpackages: + - discovery + - kubernetes + - kubernetes/scheme + - kubernetes/typed/admissionregistration/v1alpha1 + - kubernetes/typed/admissionregistration/v1beta1 + - kubernetes/typed/apps/v1 + - kubernetes/typed/apps/v1beta1 + - kubernetes/typed/apps/v1beta2 + - kubernetes/typed/authentication/v1 + - kubernetes/typed/authentication/v1beta1 + - kubernetes/typed/authorization/v1 + - kubernetes/typed/authorization/v1beta1 + - kubernetes/typed/autoscaling/v1 + - kubernetes/typed/autoscaling/v2beta1 + - kubernetes/typed/batch/v1 + - kubernetes/typed/batch/v1beta1 + - kubernetes/typed/batch/v2alpha1 + - kubernetes/typed/certificates/v1beta1 + - kubernetes/typed/core/v1 + - kubernetes/typed/events/v1beta1 + - kubernetes/typed/extensions/v1beta1 + - kubernetes/typed/networking/v1 + - kubernetes/typed/policy/v1beta1 + - kubernetes/typed/rbac/v1 + - kubernetes/typed/rbac/v1alpha1 + - kubernetes/typed/rbac/v1beta1 + - kubernetes/typed/scheduling/v1alpha1 + - kubernetes/typed/settings/v1alpha1 + - kubernetes/typed/storage/v1 + - kubernetes/typed/storage/v1alpha1 + - kubernetes/typed/storage/v1beta1 + - pkg/apis/clientauthentication + - pkg/apis/clientauthentication/v1alpha1 + - pkg/version + - plugin/pkg/client/auth/exec + - rest + - rest/watch + - tools/auth + - tools/cache + - tools/clientcmd + - tools/clientcmd/api + - tools/clientcmd/api/latest + - tools/clientcmd/api/v1 + - tools/metrics + - tools/pager + - tools/reference + - transport + - util/buffer + - util/cert + - util/flowcontrol + - util/homedir + - util/integer + - util/retry +testImports: [] diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..7228ea6 --- /dev/null +++ b/glide.yaml @@ -0,0 +1,16 @@ +package: github.com/itzg/mc-router +import: +- package: github.com/alecthomas/kingpin + version: ^2.2.6 +- package: github.com/gorilla/mux + version: ^1.6.2 +- package: github.com/pkg/errors + version: ^0.8.0 +- package: github.com/sirupsen/logrus + version: ^1.0.5 +- package: k8s.io/client-go + version: ^7.0.0 + subpackages: + - kubernetes + - tools/cache + - tools/clientcmd diff --git a/server/k8s.go b/server/k8s.go new file mode 100644 index 0000000..119452f --- /dev/null +++ b/server/k8s.go @@ -0,0 +1,122 @@ +package server + +import ( + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" + "net" +) + +type IK8sWatcher interface { + Start(kubeConfigFile string) error + Stop() +} + +var K8sWatcher IK8sWatcher = &k8sWatcherImpl{} + +type k8sWatcherImpl struct { + stop chan struct{} +} + +func (w *k8sWatcherImpl) Start(kubeConfigFile string) error { + config, err := clientcmd.BuildConfigFromFlags("", kubeConfigFile) + if err != nil { + return errors.Wrap(err, "Could not load kube config file") + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return errors.Wrap(err, "Could not create kube clientset") + } + + watchlist := cache.NewListWatchFromClient( + clientset.CoreV1().RESTClient(), + string(v1.ResourceServices), + v1.NamespaceAll, + fields.Everything(), + ) + + _, controller := cache.NewInformer( + watchlist, + &v1.Service{}, + 0, + cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { + routableService := extractRoutableService(obj) + if routableService != nil { + logrus.WithField("routableService", routableService).Debug("ADD") + + Routes.CreateMapping(routableService.externalServiceName, routableService.containerEndpoint) + } + }, + DeleteFunc: func(obj interface{}) { + routableService := extractRoutableService(obj) + if routableService != nil { + logrus.WithField("routableService", routableService).Debug("DELETE") + + Routes.DeleteMapping(routableService.externalServiceName) + } + }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldRoutableService := extractRoutableService(oldObj) + newRoutableService := extractRoutableService(newObj) + if oldRoutableService != nil && newRoutableService != nil { + logrus.WithFields(logrus.Fields{ + "old": oldRoutableService, + "new": newRoutableService, + }).Debug("UPDATE") + + Routes.DeleteMapping(oldRoutableService.externalServiceName) + Routes.CreateMapping(newRoutableService.externalServiceName, newRoutableService.containerEndpoint) + } + }, + }, + ) + + w.stop = make(chan struct{}, 1) + logrus.Info("Monitoring kubernetes for minecraft services") + go controller.Run(w.stop) + + return nil +} + +func (w *k8sWatcherImpl) Stop() { + if w.stop != nil { + w.stop <- struct{}{} + } +} + +type routableService struct { + externalServiceName string + containerEndpoint string +} + +func extractRoutableService(obj interface{}) *routableService { + service, ok := obj.(*v1.Service) + if !ok { + 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 + } + + return nil +}