Skip to content

Commit

Permalink
Hackathon/cluster (grafana#3931)
Browse files Browse the repository at this point in the history
* [wip] jsonnets upport

* jsonnet templating
  • Loading branch information
owen-d committed Jul 7, 2021
1 parent 9016eeb commit ab7ad4d
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 2 deletions.
16 changes: 16 additions & 0 deletions cmd/cluster-plan/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ type Config struct {
DaysRetention int
MonthlyUnitCost sizing.UnitCostInfo
Simple bool

// Templating
Template string
}

func (c *Config) RegisterFlags(f *flag.FlagSet) {
Expand All @@ -25,6 +28,9 @@ func (c *Config) RegisterFlags(f *flag.FlagSet) {
f.Float64Var(&c.MonthlyUnitCost.CostPerGBDisk, "monthly-cost-per-gb-disk", 0.187, "Monthly dollar cost for a GB of persistent disk")
f.Float64Var(&c.MonthlyUnitCost.CostPerGBObjStorage, "monthly-cost-per-gb-obj-storage", 0.023, "Monthly dollar cost for a GB of object storage")
f.BoolVar(&c.Simple, "simple", false, "Show a simpler compromise between resource minimums and ceilings.")

// Templating
f.StringVar(&c.Template, "template", "", "choses a template style to write cluster information to stdout instead of the text cluster plan")
}

func (c *Config) Validate() error {
Expand Down Expand Up @@ -53,6 +59,10 @@ func (c *Config) Validate() error {
return errors.New("Cannot specify negative cost per GB Object Storage")
}

if c.Template != "" && Templaters[c.Template] == nil {
return fmt.Errorf("unexpected template: %s", c.Template)
}

return nil
}

Expand All @@ -66,6 +76,12 @@ func main() {

cluster := sizing.SizeCluster(cfg.BytesPerSecond.Val())

if cfg.Template != "" {
out := Templaters[cfg.Template].Template(cluster)
fmt.Print(out)
return
}

printClusterArchitecture(&cluster, &cfg, true)
}

Expand Down
115 changes: 115 additions & 0 deletions cmd/cluster-plan/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package main

import (
"fmt"
"strings"

"github.com/grafana/loki/pkg/sizing"
)

var Templaters = map[string]Templater{
"jsonnet": JsonnetTemplater{},
}

type Templater interface {
Template(sizing.ClusterResources) string
}

type JsonnetTemplater struct{}

func (t JsonnetTemplater) Template(cluster sizing.ClusterResources) string {
var sb strings.Builder
sb.WriteString(
`local k = import 'ksonnet-util/kausal.libsonnet';
local deployment = k.apps.v1.deployment;
local statefulSet = k.apps.v1.statefulSet;
{
`)

for _, c := range cluster.Components() {
sb.WriteString(t.overrides(c))
}

sb.WriteString("}")

return sb.String()
}

func (t JsonnetTemplater) overrides(component *sizing.ComponentDescription) string {
switch component.Name {
case sizing.Distributor:
return deploymentOverrides(component, "distributor")
case sizing.Ingester:
return statefulsetOverrides(component, "ingester")
case sizing.Querier:
return deploymentOverrides(component, "querier")
case sizing.QueryFrontend:
return deploymentOverrides(component, "query_frontend")
case sizing.Ruler:
return deploymentOverrides(component, "ruler")
case sizing.Compactor:
return statefulsetOverrides(component, "compactor")
case sizing.ChunksCache:
return memcachedOverrides(component, "chunks")
case sizing.QueryResultsCache:
return memcachedOverrides(component, "frontend")
case sizing.IndexCache:
return memcachedOverrides(component, "index_queries")
case sizing.IndexGateway:
return statefulsetOverrides(component, "index_gateway")
default:
return ""
}
}

func deploymentOverrides(component *sizing.ComponentDescription, id string) string {
return fmt.Sprintf(`
%s_deployment+:
deployment.mixin.spec.withReplicas(%d),
%s_container+::
k.util.resourcesRequests('%s', '%s') +
k.util.resourcesLimits('%s', '%s'),
`,
id,
component.Replicas,
id,
component.Resources.CPURequests.Kubernetes(),
sizing.ReadableBytes(component.Resources.MemoryRequests).Kubernetes(),
component.Resources.CPULimits.Kubernetes(),
sizing.ReadableBytes(component.Resources.MemoryLimits).Kubernetes(),
)
}

func statefulsetOverrides(component *sizing.ComponentDescription, id string) string {
return fmt.Sprintf(`
%s_statefulset+:
statefulSet.mixin.spec.withReplicas(%d),
%s_container+::
$.util.resourcesRequests('%s', '%s') +
$.util.resourcesLimits('%s', '%s'),
`,
id,
component.Replicas,
id,
component.Resources.CPURequests.Kubernetes(),
sizing.ReadableBytes(component.Resources.MemoryRequests).Kubernetes(),
component.Resources.CPULimits.Kubernetes(),
sizing.ReadableBytes(component.Resources.MemoryLimits).Kubernetes(),
)
}

func memcachedOverrides(component *sizing.ComponentDescription, id string) string {
// Everything but replicas are actually managed by the memcached jsonnet library
// and our memcached limits are derived from these, so avoid the complexity of trying to override it.
return fmt.Sprintf(`
memcached_%s+: {
statefulSet+:
statefulSet.mixin.spec.withReplicas(%d),
},
`,
id,
component.Replicas,
)
}
31 changes: 31 additions & 0 deletions pkg/sizing/cpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,34 @@ func (b ReadableBytes) String() string {
}
return fmt.Sprintf("%.2f%s", x, order[degree])
}

const (
B ReadableBytes = 1
KB = B << 10
MB = KB << 10
GB = MB << 10
TB = GB << 10
PB = TB << 10
EB = PB << 10
)

func (b ReadableBytes) Kubernetes() string {
switch {
case b == 0:
return "0B"
case b%EB == 0:
return fmt.Sprintf("%dEi", b/EB)
case b%PB == 0:
return fmt.Sprintf("%dPi", b/PB)
case b%TB == 0:
return fmt.Sprintf("%dTi", b/TB)
case b%GB == 0:
return fmt.Sprintf("%dGi", b/GB)
case b%MB == 0:
return fmt.Sprintf("%dMi", b/MB)
case b%KB == 0:
return fmt.Sprintf("%dKi", b/KB)
default:
return fmt.Sprintf("%dB", b)
}
}
4 changes: 2 additions & 2 deletions pkg/sizing/sizing.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ func distributorSizing(ingestionRateMB float64) *ComponentDescription {

component.Name = Distributor
component.Replicas = n
_ = component.Resources.MemoryRequests.Set("500KB")
_ = component.Resources.MemoryRequests.Set("500MB")
_ = component.Resources.MemoryLimits.Set("1GB")
component.Resources.CPURequests.SetCores(0.5)
component.Resources.CPULimits.SetCores(0.5)
component.Resources.CPULimits.SetCores(1)
return component
}

Expand Down

0 comments on commit ab7ad4d

Please sign in to comment.