From f522e0833524c1554fd8dc84aa21273dd4c57deb Mon Sep 17 00:00:00 2001 From: Greg Clark Date: Mon, 10 Jun 2019 13:12:43 -0400 Subject: [PATCH 1/2] Merge apps by label - This allows for example, multiple implementations of a service to be accessed using the same address even though they are running as different apps within mesos/marathon. - Users specify a label which can be used as the service name across multiple apps, and then call MergeAppsByLabel within their template. - When the apps are merged Labels from each original app are added to each Task from that original app. This is necessary if you want to have implementation specific labels. For example, if one implementation is faster, we could route more traffic there. --- nixy.go | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/nixy.go b/nixy.go index 60298b8..b2bf6f1 100644 --- a/nixy.go +++ b/nixy.go @@ -30,6 +30,7 @@ type Task struct { StartedAt string State string Version string + Labels map[string]string } // PortDefinitions struct @@ -141,6 +142,63 @@ var eventqueue = make(chan bool, 2) // Global http transport for connection reuse var tr = &http.Transport{MaxIdleConnsPerHost: 10} +func (c *Config) MergeAppsByLabel(label string) map[string]App { + apps := make(map[string]App, 0) + labeledApps := make(map[string][]App, 0) + for appID, app := range c.Apps { + if labelValue, has := app.Labels[label]; has { + labeledApps[labelValue] = append(labeledApps[labelValue], app) + } else { + apps[appID] = app + } + } + + for id, appGroup := range labeledApps { + apps[id] = mergeApps(appGroup) + } + return apps +} + +func mergeApps(apps []App) App { + tasks := make([]Task, 0) + labels := make(map[string]string, 0) + env := make(map[string]string, 0) + hosts := make([]string, 0) + portDefs := make([]PortDefinitions, 0) + seenPorts := make(map[int64]bool, 0) + + for _, app := range apps { + for k, v := range app.Labels { + labels[k] = v + } + for k, v := range app.Env { + env[k] = v + } + for _, h := range app.Hosts { + hosts = append(hosts, h) + } + for _, t := range app.Tasks { + t.Labels = app.Labels + tasks = append(tasks, t) + } + for _, def := range app.PortDefinitions { + if _, seen := seenPorts[def.Port]; !seen { + seenPorts[def.Port] = true + portDefs = append(portDefs, def) + } + } + } + return App{ + Tasks: tasks, + Labels: labels, + Env: env, + Hosts: hosts, + PortDefinitions: portDefs, + HealthChecks: apps[0].HealthChecks, + Container: Container{}, + } +} + func newHealth() Health { var h Health for _, ep := range config.Marathon { From 39f143670b58b465616c02ffe6bd7938cdc6b6f4 Mon Sep 17 00:00:00 2001 From: Greg Clark Date: Fri, 28 Jun 2019 12:24:01 -0400 Subject: [PATCH 2/2] Document MargeAppsByLabel --- README.md | 19 ++++++++++++++++++ nginx-merge-app-by-id.tmpl | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 nginx-merge-app-by-id.tmpl diff --git a/README.md b/README.md index 200557f..1052085 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,25 @@ Alias for [time.Now](https://golang.org/pkg/time/#Now) # Generated by nixy {{datetime}} ``` +#### MergeAppsByLabel + +Sometimes it is useful to implement the same service with apps within marathon. +In this case you can use `.MergeAppsByLabel("some-label")` instead of `.Apps` in your template +to merge multiple apps into a single service. + +For example if the following apps running in marathon implement the same API: + +`my-service-a` +`my-service-b` + +And they both have the label `servicename=my-service` you could use `MergeAppsByLabel("servicename")` +to access both implementations as `nixy.marathon.mesos:12345`. + +- When the apps are merged, `Labels` from each original app are added + to each Task from that original app. This is necessary if you want + to have implementation specific labels. For example, if one + implementation is faster, we could route more traffic there. + ### TCP/UDP Load Balancing / Proxy It is possible to use Nixy to configure nginx as a proxy for TCP or UDP traffic. diff --git a/nginx-merge-app-by-id.tmpl b/nginx-merge-app-by-id.tmpl new file mode 100644 index 0000000..f6ceb23 --- /dev/null +++ b/nginx-merge-app-by-id.tmpl @@ -0,0 +1,40 @@ +# Generated by nixy {{datetime}} +user www-data; +worker_processes auto; + +pid /var/run/nginx.pid + +events { + use epoll; + worker_connections 2048; + multi_accept on; +} + +stream { + {{- range $appid, $app := .MergeAppsByLabel "streamservicename"}} + {{- if eq (index $app.Labels "internal") "stream"}} + {{- range $id, $definition := $app.PortDefinitions}} + {{- if ne (index $app.Labels "streamservicename") ""}} + upstream {{ (index $app.Labels "streamservicename") }}-{{ $id }} { + {{- else}} + upstream {{index $app.Hosts 0}}-{{ $id }} { + {{- end}} + least_conn; + {{- range $task := $app.Tasks}} + server {{ $task.Host }}:{{ index $task.Ports $id}}{{- with index $task.Labels "weight"}} weight={{ . }}{{- end}}; + {{- end}} + } + server { + {{- if eq $definition.Protocol "tcp" }} + listen {{ $definition.Port }}; + {{- else}} + listen {{ $definition.Port }} {{ $definition.Protocol }}; + {{- end}} + {{- if ne (index $app.Labels "streamservicename") ""}} + proxy_pass {{ (index $app.Labels "streamservicename") }}-{{ $id }}; + {{- else}} + proxy_pass {{index $app.Hosts 0}}-{{ $id }}; + {{- end}} + } +} +