Skip to content

Commit

Permalink
Feat/prometheus autoscale (#223)
Browse files Browse the repository at this point in the history
* feat: keda prometheus autoscaler

* test: keda prometheus autoscaler

* bump: go-tsuruclient version
  • Loading branch information
crgisch authored Apr 11, 2024
1 parent 1e295e0 commit 0c5e922
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 24 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ require (
github.com/pmorie/go-open-service-broker-client v0.0.0-20180330214919-dca737037ce6
github.com/sabhiram/go-gitignore v0.0.0-20171017070213-362f9845770f
github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa
github.com/tsuru/go-tsuruclient v0.0.0-20240403173008-e8ea92a75f9b
github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4
github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6
github.com/tsuru/tsuru v0.0.0-20240325190920-410c71393b77
golang.org/x/net v0.20.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -675,8 +675,8 @@ github.com/tsuru/config v0.0.0-20201023175036-375aaee8b560 h1:fniQ/BmYAHdnNmY333
github.com/tsuru/config v0.0.0-20201023175036-375aaee8b560/go.mod h1:mj6t8JKWU51GScTT50XRmDj65T5XhTyNvO5FUNV5zS4=
github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa h1:JlLQP1xa13a994p/Aau2e3K9xXYaHNoNvTDVIMHSUa4=
github.com/tsuru/gnuflag v0.0.0-20151217162021-86b8c1b864aa/go.mod h1:UibOSvkMFKRe/eiwktAPAvQG8L+p8nYsECJvu3Dgw7I=
github.com/tsuru/go-tsuruclient v0.0.0-20240403173008-e8ea92a75f9b h1:De7rIp7qigWFMpdxukKcUPFDjacmwN4drji6i4X+hT0=
github.com/tsuru/go-tsuruclient v0.0.0-20240403173008-e8ea92a75f9b/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18=
github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4 h1:MGmG6AxKP8XRe7nQqIQR+Tsb5tCzHnYpYk0tiuXVgxY=
github.com/tsuru/go-tsuruclient v0.0.0-20240409125509-22a1e08326f4/go.mod h1:qwh/KJ6ypa2GISRI79XFOHhnSjGOe1cZVPHF3nfrf18=
github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6 h1:1XDdWFAjIbCSG1OjN9v9KdWhuM8UtYlFcfHe/Ldkchk=
github.com/tsuru/tablecli v0.0.0-20190131152944-7ded8a3383c6/go.mod h1:ztYpOhW+u1k21FEqp7nZNgpWbr0dUKok5lgGCZi+1AQ=
github.com/tsuru/tsuru v0.0.0-20240325190920-410c71393b77 h1:cuWFjNLaemdQZhojqJbb/rOXO97tlcPeLAHg/x+EQGk=
Expand Down
16 changes: 16 additions & 0 deletions tsuru/client/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,14 @@ func (a *app) String(simplified bool) string {
}))
}

for _, prometheus := range as.Prometheus {
prometheusInfo := buildPrometheusInfo(prometheus)
autoScaleTable.AddRow(tablecli.Row([]string{
"Prometheus",
prometheusInfo,
}))
}

autoScaleTables = append(autoScaleTables, autoScaleTable)
}

Expand Down Expand Up @@ -847,6 +855,14 @@ func buildScheduleInfo(schedule tsuru.AutoScaleSchedule) string {
)
}

func buildPrometheusInfo(prometheus tsuru.AutoScalePrometheus) string {
thresholdValue := strconv.FormatFloat(prometheus.Threshold, 'f', -1, 64)

return fmt.Sprintf("Name: %s\nQuery: %s\nThreshold: %s\nPrometheusAddress: %s",
prometheus.Name, prometheus.Query, thresholdValue, prometheus.PrometheusAddress,
)
}

func (a *app) SimpleServicesView() string {
sibs := make([]tsuru.AppServiceInstanceBinds, len(a.ServiceInstanceBinds))
copy(sibs, a.ServiceInstanceBinds)
Expand Down
53 changes: 38 additions & 15 deletions tsuru/client/apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1684,6 +1684,19 @@ func (s *S) TestAppInfoWithKEDAAutoScale(c *check.C) {
"end":"0 15 * * *",
"timezone":"UTC"
}
],
"prometheus": [
{
"name":"my_metric_id",
"threshold":2,
"query":"my_query{app='my-app'}",
"prometheusAddress":"default.com"
},{
"name":"my_metric_id_2",
"threshold":5,
"query":"my_query_2{app='my-app'}",
"prometheusAddress":"exemple.prometheus.com"
}
]
},
{
Expand Down Expand Up @@ -1730,21 +1743,31 @@ Units [process worker]: 1
Auto Scale:
Process: web (v10), Min Units: 1, Max Units: 10
+----------+---------------------------------+
| Triggers | Trigger details |
+----------+---------------------------------+
| CPU | Target: 50% |
+----------+---------------------------------+
| Schedule | Start: At 06:00 AM (0 6 * * *) |
| | End: At 06:00 PM (0 18 * * *) |
| | Units: 2 |
| | Timezone: UTC |
+----------+---------------------------------+
| Schedule | Start: At 12:00 PM (0 12 * * *) |
| | End: At 03:00 PM (0 15 * * *) |
| | Units: 3 |
| | Timezone: UTC |
+----------+---------------------------------+
+------------+-------------------------------------------+
| Triggers | Trigger details |
+------------+-------------------------------------------+
| CPU | Target: 50% |
+------------+-------------------------------------------+
| Schedule | Start: At 06:00 AM (0 6 * * *) |
| | End: At 06:00 PM (0 18 * * *) |
| | Units: 2 |
| | Timezone: UTC |
+------------+-------------------------------------------+
| Schedule | Start: At 12:00 PM (0 12 * * *) |
| | End: At 03:00 PM (0 15 * * *) |
| | Units: 3 |
| | Timezone: UTC |
+------------+-------------------------------------------+
| Prometheus | Name: my_metric_id |
| | Query: my_query{app='my-app'} |
| | Threshold: 2 |
| | PrometheusAddress: default.com |
+------------+-------------------------------------------+
| Prometheus | Name: my_metric_id_2 |
| | Query: my_query_2{app='my-app'} |
| | Threshold: 5 |
| | PrometheusAddress: exemple.prometheus.com |
+------------+-------------------------------------------+
Process: worker (v10), Min Units: 2, Max Units: 5
+----------+--------------------------------+
Expand Down
28 changes: 23 additions & 5 deletions tsuru/client/autoscale.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,16 @@ func (i *int32Value) String() string { return fmt.Sprintf("%v", *i) }

type AutoScaleSet struct {
tsuruClientApp.AppNameMixIn
fs *gnuflag.FlagSet
autoscale tsuru.AutoScaleSpec
schedules cmd.StringSliceFlag
fs *gnuflag.FlagSet
autoscale tsuru.AutoScaleSpec
schedules cmd.StringSliceFlag
prometheus cmd.StringSliceFlag
}

func (c *AutoScaleSet) Info() *cmd.Info {
return &cmd.Info{
Name: "unit-autoscale-set",
Usage: "unit autoscale set [-a/--app appname] [-p/--process processname] [--cpu targetCPU] [--min minUnits] [--max maxUnits] [--schedule scheduleWindow]",
Usage: "unit autoscale set [-a/--app appname] [-p/--process processname] [--cpu targetCPU] [--min minUnits] [--max maxUnits] [--schedule scheduleWindow] [--prometheus prometheusSettings]",
Desc: `
# Sets an autoscale configuration:
# Based on 50% of CPU utilization with min units 1 and max units 3
Expand All @@ -46,7 +47,11 @@ unit autoscale set -a my-app --cpu 50% --min 1 --max 3
# Based on a schedule window everyday from 6AM to 6PM UTC
unit autoscale set -a my-app --min 1 --max 3 --schedule '{"minReplicas": 2, "start": "0 6 * * *", "end": "0 18 * * *"}'
# Combining both
# Based on a prometheus metric
unit autoscale set -a my-app --min 1 --max 3 --prometheus '{"name": "my_metric_identification", "threshold": 10, "query":"sum(my_metric{tsuru_app=\"my_app\"})"}'
# Combining
unit autoscale set -a my-app --cpu 50% --min 1 --max 3 --schedule '{"minReplicas": 2, "start": "0 6 * * *", "end": "0 18 * * *"}'
# When using more than one trigger (CPU + Schedule as an example), the number of units will be determined by the highest value
Expand All @@ -71,6 +76,7 @@ func (c *AutoScaleSet) Flags() *gnuflag.FlagSet {
c.fs.Var((*int32Value)(&c.autoscale.MaxUnits), "max", "Maximum Units")

c.fs.Var(&c.schedules, "schedule", "Schedule window to up/down scale. Example: {\"minReplicas\": 2, \"start\": \"0 6 * * *\", \"end\": \"0 18 * * *\"}")
c.fs.Var(&c.prometheus, "prometheus", "Prometheus settings to up/down scale. Example: {\"name\": \"my_metric_identification\", \"threshold\": 10, \"query\":\"sum(my_metric{tsuru_app=\\\"my_app\\\"})\"}")
}
return c.fs
}
Expand All @@ -97,6 +103,18 @@ func (c *AutoScaleSet) Run(ctx *cmd.Context) error {

c.autoscale.Schedules = schedules

prometheus := []tsuru.AutoScalePrometheus{}
for _, prometheusString := range c.prometheus {
var autoScalePrometheus tsuru.AutoScalePrometheus
if err = json.Unmarshal([]byte(prometheusString), &autoScalePrometheus); err != nil {
return err
}

prometheus = append(prometheus, autoScalePrometheus)
}

c.autoscale.Prometheus = prometheus

_, err = apiClient.AppApi.AutoScaleAdd(context.TODO(), appName, c.autoscale)
if err != nil {
return err
Expand Down
55 changes: 54 additions & 1 deletion tsuru/client/autoscale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func (s *S) TestAutoScaleSet(c *check.C) {
c.Assert(stdout.String(), check.Equals, expected)
}

func (s *S) TestKEDAAutoScaleSet(c *check.C) {
func (s *S) TestKEDAScheduleAutoScaleSet(c *check.C) {
var stdout, stderr bytes.Buffer
expected := "Unit auto scale successfully set.\n"
context := cmd.Context{
Expand Down Expand Up @@ -106,6 +106,59 @@ func (s *S) TestKEDAAutoScaleSet(c *check.C) {
c.Assert(stdout.String(), check.Equals, expected)
}

func (s *S) TestKEDAPrometheusAutoScaleSet(c *check.C) {
var stdout, stderr bytes.Buffer
expected := "Unit auto scale successfully set.\n"
context := cmd.Context{
Stdout: &stdout,
Stderr: &stderr,
Args: []string{},
}
trans := cmdtest.ConditionalTransport{
Transport: cmdtest.Transport{Message: "", Status: http.StatusOK},
CondFunc: func(r *http.Request) bool {
c.Assert(r.URL.Path, check.Equals, "/1.9/apps/myapp/units/autoscale")
c.Assert(r.Method, check.Equals, "POST")
var ret tsuru.AutoScaleSpec
c.Assert(r.Header.Get("Content-Type"), check.Equals, "application/json")
data, err := io.ReadAll(r.Body)
c.Assert(err, check.IsNil)
err = json.Unmarshal(data, &ret)
c.Assert(err, check.IsNil)
c.Assert(ret, check.DeepEquals, tsuru.AutoScaleSpec{
MinUnits: 1,
MaxUnits: 5,
Process: "proc1",
Prometheus: []tsuru.AutoScalePrometheus{
{
Name: "prometheus_metric_1",
Threshold: 1,
Query: "my_metric_1(app='my-app')",
},
{
Name: "prometheus_metric_2",
Threshold: 5,
Query: "my_metric_2(app='my-app')",
PrometheusAddress: "exemple.prometheus.com",
},
},
})
return true
},
}
s.setupFakeTransport(&trans)
command := AutoScaleSet{}
command.Info()
command.Flags().Parse(true, []string{
"-a", "myapp", "-p", "proc1", "--min", "1", "--max", "5",
"--prometheus", "{\"name\": \"prometheus_metric_1\", \"threshold\": 1, \"query\": \"my_metric_1(app='my-app')\"}",
"--prometheus", "{\"name\": \"prometheus_metric_2\", \"threshold\": 5, \"query\": \"my_metric_2(app='my-app')\", \"prometheusAddress\": \"exemple.prometheus.com\"}",
})
err := command.Run(&context)
c.Assert(err, check.IsNil)
c.Assert(stdout.String(), check.Equals, expected)
}

func (s *S) TestAutoScaleUnset(c *check.C) {
var stdout, stderr bytes.Buffer
expected := "Unit auto scale successfully unset.\n"
Expand Down

0 comments on commit 0c5e922

Please sign in to comment.