diff --git a/README.md b/README.md index 21d1357..cd6ef7c 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,42 @@ Routes Minecraft client connections to backend servers based upon the requested # Usage ```text -Flags: - --help Show context-sensitive help (also try --help-long - and --help-man). - --port=25565 The port bound to listen for Minecraft client - connections - --api-binding=API-BINDING The host:port bound for servicing API requests - --mapping=MAPPING,MAPPING Where MAPPING is externalHostname=host:port + -api-binding host:port + The host:port bound for servicing API requests (env API_BINDING) + -connection-rate-limit int + Max number of connections to allow per second (env CONNECTION_RATE_LIMIT) (default 1) + -cpu-profile string + Enables CPU profiling and writes to given path (env CPU_PROFILE) + -debug + Enable debug logs (env DEBUG) + -in-kube-cluster + Use in-cluster kubernetes config (env IN_KUBE_CLUSTER) + -kube-config string + The path to a kubernetes configuration file (env KUBE_CONFIG) + -kube-discovery + Enables discovery of annotated kubernetes services (env KUBE_DISCOVERY) + -mapping string + Comma-separated mappings of externalHostname=host:port (env MAPPING) + -metrics-backend string + Backend to use for metrics exposure/publishing: discard,expvar,influxdb (env METRICS_BACKEND) (default "discard") + -metrics-backend-config-influxdb-addr string + (env METRICS_BACKEND_CONFIG_INFLUXDB_ADDR) + -metrics-backend-config-influxdb-database string + (env METRICS_BACKEND_CONFIG_INFLUXDB_DATABASE) + -metrics-backend-config-influxdb-interval duration + (env METRICS_BACKEND_CONFIG_INFLUXDB_INTERVAL) (default 1m0s) + -metrics-backend-config-influxdb-password string + (env METRICS_BACKEND_CONFIG_INFLUXDB_PASSWORD) + -metrics-backend-config-influxdb-retention-policy string + (env METRICS_BACKEND_CONFIG_INFLUXDB_RETENTION_POLICY) + -metrics-backend-config-influxdb-tags value + any extra tags to be included with all reported metrics (env METRICS_BACKEND_CONFIG_INFLUXDB_TAGS) + -metrics-backend-config-influxdb-username string + (env METRICS_BACKEND_CONFIG_INFLUXDB_USERNAME) + -port port + The port bound to listen for Minecraft client connections (env PORT) (default 25565) + -version + Output version and exit (env VERSION) ``` # REST API diff --git a/cmd/mc-router/main.go b/cmd/mc-router/main.go index e0b4bac..888c38b 100644 --- a/cmd/mc-router/main.go +++ b/cmd/mc-router/main.go @@ -13,19 +13,34 @@ import ( "strconv" "strings" "syscall" + "time" ) +type MetricsBackendConfig struct { + Influxdb struct { + Interval time.Duration `default:"1m"` + Tags map[string]string `usage:"any extra tags to be included with all reported metrics"` + Addr string + Username string + Password string + Database string + RetentionPolicy string + } +} + type Config struct { - Port int `default:"25565" usage:"The [port] bound to listen for Minecraft client connections"` - Mapping string `usage:"Comma-separated mappings of externalHostname=host:port"` - ApiBinding string `usage:"The [host:port] bound for servicing API requests"` - Version bool `usage:"Output version and exit"` - CpuProfile string `usage:"Enables CPU profiling and writes to given path"` - Debug bool `usage:"Enable debug logs"` - ConnectionRateLimit int `default:"1" usage:"Max number of connections to allow per second"` - InKubeCluster bool `usage:"Use in-cluster kubernetes config"` - KubeConfig string `usage:"The path to a kubernetes configuration file"` - MetricsBackend string `default:"discard" usage:"Backend to use for metrics exposure/publishing: discard,expvar"` + Port int `default:"25565" usage:"The [port] bound to listen for Minecraft client connections"` + Mapping string `usage:"Comma-separated mappings of externalHostname=host:port"` + ApiBinding string `usage:"The [host:port] bound for servicing API requests"` + Version bool `usage:"Output version and exit"` + CpuProfile string `usage:"Enables CPU profiling and writes to given path"` + Debug bool `usage:"Enable debug logs"` + ConnectionRateLimit int `default:"1" usage:"Max number of connections to allow per second"` + KubeDiscovery bool `usage:"Enables discovery of annotated kubernetes services"` + InKubeCluster bool `usage:"Use in-cluster kubernetes config"` + KubeConfig string `usage:"The path to a kubernetes configuration file"` + MetricsBackend string `default:"discard" usage:"Backend to use for metrics exposure/publishing: discard,expvar,influxdb"` + MetricsBackendConfig MetricsBackendConfig } var ( @@ -71,8 +86,9 @@ func main() { } ctx, cancel := context.WithCancel(context.Background()) + defer cancel() - metricsBuilder := NewMetricsBuilder(config.MetricsBackend) + metricsBuilder := NewMetricsBuilder(config.MetricsBackend, &config.MetricsBackendConfig) c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) @@ -98,22 +114,27 @@ func main() { if config.InKubeCluster { err = server.K8sWatcher.StartInCluster() if err != nil { - logrus.WithError(err).Warn("Unable to start k8s integration") + logrus.WithError(err).Fatal("Unable to start k8s integration") } else { defer server.K8sWatcher.Stop() } } else if config.KubeConfig != "" { err := server.K8sWatcher.StartWithConfig(config.KubeConfig) if err != nil { - logrus.WithError(err).Warn("Unable to start k8s integration") + logrus.WithError(err).Fatal("Unable to start k8s integration") } else { defer server.K8sWatcher.Stop() } } + err = metricsBuilder.Start(ctx) + if err != nil { + logrus.WithError(err).Fatal("Unable to start metrics reporter") + } + + // wait for process-stop signal <-c logrus.Info("Stopping") - cancel() } func parseMappings(val string) map[string]string { diff --git a/cmd/mc-router/metrics.go b/cmd/mc-router/metrics.go index eeeb286..dfc6bf3 100644 --- a/cmd/mc-router/metrics.go +++ b/cmd/mc-router/metrics.go @@ -1,31 +1,44 @@ package main import ( + "context" + "errors" + "fmt" + kitlogrus "github.com/go-kit/kit/log/logrus" discardMetrics "github.com/go-kit/kit/metrics/discard" expvarMetrics "github.com/go-kit/kit/metrics/expvar" + kitinflux "github.com/go-kit/kit/metrics/influx" + influx "github.com/influxdata/influxdb1-client/v2" "github.com/itzg/mc-router/server" "github.com/sirupsen/logrus" + "strings" + "time" ) type MetricsBuilder interface { BuildConnectorMetrics() *server.ConnectorMetrics + Start(ctx context.Context) error } -func NewMetricsBuilder(backend string) MetricsBuilder { - switch backend { - case "discard": - return &discardMetricsBuilder{} +func NewMetricsBuilder(backend string, config *MetricsBackendConfig) MetricsBuilder { + switch strings.ToLower(backend) { case "expvar": return &expvarMetricsBuilder{} + case "influxdb": + return &influxMetricsBuilder{config: config} default: - logrus.Fatalf("Unsupported metrics backend: %s", backend) - return nil + return &discardMetricsBuilder{} } } type expvarMetricsBuilder struct { } +func (b expvarMetricsBuilder) Start(ctx context.Context) error { + // nothing needed + return nil +} + func (b expvarMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetrics { return &server.ConnectorMetrics{ Errors: expvarMetrics.NewCounter("errors").With("subsystem", "connector"), @@ -38,6 +51,11 @@ func (b expvarMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetrics { type discardMetricsBuilder struct { } +func (b discardMetricsBuilder) Start(ctx context.Context) error { + // nothing needed + return nil +} + func (b discardMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetrics { return &server.ConnectorMetrics{ Errors: discardMetrics.NewCounter(), @@ -46,3 +64,50 @@ func (b discardMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetrics ActiveConnections: discardMetrics.NewGauge(), } } + +type influxMetricsBuilder struct { + config *MetricsBackendConfig + metrics *kitinflux.Influx +} + +func (b *influxMetricsBuilder) Start(ctx context.Context) error { + influxConfig := &b.config.Influxdb + if influxConfig.Addr == "" { + return errors.New("influx addr is required") + } + + ticker := time.NewTicker(influxConfig.Interval) + client, err := influx.NewHTTPClient(influx.HTTPConfig{ + Addr: influxConfig.Addr, + Username: influxConfig.Username, + Password: influxConfig.Password, + }) + if err != nil { + return fmt.Errorf("failed to create influx http client: %w", err) + } + + go b.metrics.WriteLoop(ctx, ticker.C, client) + + logrus.WithField("addr", influxConfig.Addr). + Debug("reporting metrics to influxdb") + + return nil +} + +func (b *influxMetricsBuilder) BuildConnectorMetrics() *server.ConnectorMetrics { + influxConfig := &b.config.Influxdb + + metrics := kitinflux.New(influxConfig.Tags, influx.BatchPointsConfig{ + Database: influxConfig.Database, + RetentionPolicy: influxConfig.RetentionPolicy, + }, kitlogrus.NewLogrusLogger(logrus.StandardLogger())) + + b.metrics = metrics + + return &server.ConnectorMetrics{ + Errors: metrics.NewCounter("errors"), + BytesTransmitted: metrics.NewCounter("transmitted_bytes"), + Connections: metrics.NewCounter("connections"), + ActiveConnections: metrics.NewGauge("connections_active"), + } +} diff --git a/go.mod b/go.mod index 36f28c4..1b7a7d0 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,8 @@ go 1.12 require ( github.com/VividCortex/gohistogram v1.0.0 // indirect github.com/go-kit/kit v0.9.0 + github.com/go-logfmt/logfmt v0.5.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect github.com/gogo/protobuf v1.2.1 // indirect github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect github.com/golang/protobuf v1.3.1 // indirect @@ -13,7 +15,8 @@ require ( github.com/gorilla/mux v1.7.1 github.com/hashicorp/golang-lru v0.5.1 // indirect github.com/imdario/mergo v0.3.7 // indirect - github.com/itzg/go-flagsfiller v1.4.1 + github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d + github.com/itzg/go-flagsfiller v1.4.2 github.com/json-iterator/go v1.1.6 // indirect github.com/juju/ratelimit v1.0.1 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect diff --git a/go.sum b/go.sum index 44a1f60..b5cdda6 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,10 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= @@ -25,8 +29,12 @@ github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFE github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d h1:/WZQPMZNsjZ7IlCpsLGdQBINg5bxKQ1K1sh6awxLtkA= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/itzg/go-flagsfiller v1.4.1 h1:h/t5g+WkvsOR449bz1ngU8UGosKNm4Sr3iMNNgOqHfo= github.com/itzg/go-flagsfiller v1.4.1/go.mod h1:mfQgTahSs4OHn8PYev2Wwi1LJXUiYiGuZVCpBLxzbYs= +github.com/itzg/go-flagsfiller v1.4.2 h1:aQRyQ9/ps111FZYGhzxgKYiOewE7zbAks7rxF49Qlm8= +github.com/itzg/go-flagsfiller v1.4.2/go.mod h1:mfQgTahSs4OHn8PYev2Wwi1LJXUiYiGuZVCpBLxzbYs= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/juju/ratelimit v1.0.1 h1:+7AIFJVQ0EQgq/K9+0Krm7m530Du7tIz0METWzN0RgY= diff --git a/test/mc-router-telegraf-backend/docker-compose.yml b/test/mc-router-telegraf-backend/docker-compose.yml new file mode 100644 index 0000000..6bcf270 --- /dev/null +++ b/test/mc-router-telegraf-backend/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3' + +services: + telegraf: + image: telegraf:1.13 + ports: + - 8186:8186 + volumes: + - ./telegraf.conf:/etc/telegraf/telegraf.conf:ro diff --git a/test/mc-router-telegraf-backend/telegraf.conf b/test/mc-router-telegraf-backend/telegraf.conf new file mode 100644 index 0000000..2316d3b --- /dev/null +++ b/test/mc-router-telegraf-backend/telegraf.conf @@ -0,0 +1,8 @@ +[agent] + interval = "10s" + +[[inputs.influxdb_listener]] + service_address = ":8186" + +[[outputs.file]] + files = ["stdout"] \ No newline at end of file