Skip to content

Commit 1379eda

Browse files
committed
Keep track of desired scale
1 parent a13a7a4 commit 1379eda

File tree

6 files changed

+186
-38
lines changed

6 files changed

+186
-38
lines changed

BUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ go_library(
1010
importpath = "github.com/jdewinne/k8s-dev-scaler",
1111
visibility = ["//visibility:private"],
1212
deps = [
13-
"@io_k8s_apimachinery//pkg/apis/meta/v1:meta",
13+
"//scaler",
1414
"@io_k8s_client_go//kubernetes",
1515
"@io_k8s_client_go//rest",
1616
"@io_k8s_client_go//tools/clientcmd",

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
22

33
When developing on a local k8s instance, often you have to juggle with memory, cpu, ... And when developing with multiple branches, you sometimes have your app installed in multiple namespaces. Each branch, having it's own namespace maybe...
44

5-
So in order to reduce your resource consumption by your k8s dev cluster, this tool allows to downscale all `deployments` and `statefulsets` to zero. It also allows to put them all back at scale `1`.
5+
So in order to reduce your resource consumption by your k8s dev cluster, this tool allows to downscale all `deployments` and `statefulsets` to zero. It also allows to scale them all back up. Behind the scenes it places an annotaion called `k8s.dev.scaler/desired.replicas` that keeps track of the desired number if replicas.
6+
7+
# Installation
8+
9+
+ Linux: Download from [Releases](https://github.com/jdewinne/k8s-dev-scaler/releases)
10+
+ Linux, Mac: Install using `go get https://github.com/jdewinne/k8s-dev-scaler`
611

712
# Usage
813

main.go

Lines changed: 6 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
package main
22

33
import (
4-
"context"
54
"flag"
65
"fmt"
76

8-
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
97
"k8s.io/client-go/kubernetes"
108
"k8s.io/client-go/rest"
119
"k8s.io/client-go/tools/clientcmd"
10+
11+
scaler "github.com/jdewinne/k8s-dev-scaler/scaler"
1212
)
1313

1414
// GetKubeClient creates a Kubernetes config and client for a given kubeconfig context.
@@ -67,48 +67,18 @@ func main() {
6767
panic("Scale must be up or down")
6868
}
6969

70-
replicas := int32(0)
71-
if *scale == "up" {
72-
replicas = 1
73-
}
74-
7570
// use the current context in kubeconfig
7671
_, client, err := GetKubeClient(*k8scontextflag)
7772
if err != nil {
7873
panic(err.Error())
7974
}
8075

8176
fmt.Println("Deployments")
82-
83-
deploymentsClient := client.AppsV1().Deployments(*namespace)
84-
list, err := deploymentsClient.List(context.TODO(), metav1.ListOptions{})
85-
if err != nil {
86-
panic(err)
87-
}
88-
for _, d := range list.Items {
89-
fmt.Printf(" * Scaling %s (%d to %d replicas)\n", d.Name, *d.Spec.Replicas, replicas)
90-
opts, err := deploymentsClient.GetScale(context.TODO(), d.Name, metav1.GetOptions{})
91-
if err != nil {
92-
panic(err)
93-
}
94-
opts.Spec.Replicas = replicas
95-
deploymentsClient.UpdateScale(context.TODO(), d.Name, opts, metav1.UpdateOptions{})
96-
}
77+
dscaler := scaler.NewDeploymentsScaler(client, *namespace, *scale)
78+
dscaler.ScaleDeploymentResources()
9779

9880
fmt.Println("Stateful sets")
99-
statefulSetsClient := client.AppsV1().StatefulSets(*namespace)
100-
sslist, err := statefulSetsClient.List(context.TODO(), metav1.ListOptions{})
101-
if err != nil {
102-
panic(err)
103-
}
104-
for _, ss := range sslist.Items {
105-
fmt.Printf(" * Scaling %s (%d to %d replicas)\n", ss.Name, *ss.Spec.Replicas, replicas)
106-
opts, err := statefulSetsClient.GetScale(context.TODO(), ss.Name, metav1.GetOptions{})
107-
if err != nil {
108-
panic(err)
109-
}
110-
opts.Spec.Replicas = replicas
111-
statefulSetsClient.UpdateScale(context.TODO(), ss.Name, opts, metav1.UpdateOptions{})
112-
}
81+
sscaler := scaler.NewStatefulSetsScaler(client, *namespace, *scale)
82+
sscaler.ScaleStatefulSetResources()
11383

11484
}

scaler/BUILD.bazel

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library")
2+
3+
go_library(
4+
name = "scaler",
5+
srcs = [
6+
"deployments.go",
7+
"statefulSets.go",
8+
],
9+
importpath = "github.com/jdewinne/k8s-dev-scaler/scaler",
10+
visibility = ["//visibility:public"],
11+
deps = [
12+
"@io_k8s_apimachinery//pkg/apis/meta/v1:meta",
13+
"@io_k8s_apimachinery//pkg/types",
14+
"@io_k8s_client_go//kubernetes",
15+
"@io_k8s_client_go//kubernetes/typed/apps/v1:apps",
16+
],
17+
)

scaler/deployments.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package scaler
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/types"
10+
"k8s.io/client-go/kubernetes"
11+
v1 "k8s.io/client-go/kubernetes/typed/apps/v1"
12+
)
13+
14+
// DeploymentsScaler allows to scale up or down all deployments
15+
type DeploymentsScaler struct {
16+
17+
// defining struct variables
18+
client v1.DeploymentInterface
19+
namespace string
20+
scale string
21+
}
22+
23+
// NewDeploymentsScaler instantiates
24+
func NewDeploymentsScaler(client kubernetes.Interface, namespace string, scale string) *DeploymentsScaler {
25+
p := new(DeploymentsScaler)
26+
p.client = client.AppsV1().Deployments(namespace)
27+
p.namespace = namespace
28+
p.scale = scale
29+
return p
30+
}
31+
32+
func (ds *DeploymentsScaler) annotateResource(name string, replicas int32) error {
33+
payload := fmt.Sprintf(`{"metadata":{"annotations":{"k8s.dev.scaler/desired.replicas":"%d"}}}`, replicas)
34+
_, err := ds.client.Patch(context.TODO(), name, types.MergePatchType, []byte(payload), metav1.PatchOptions{})
35+
return err
36+
}
37+
38+
func (ds *DeploymentsScaler) getDesiredReplicas(name string) (int32, error) {
39+
deployment, err := ds.client.Get(context.TODO(), name, metav1.GetOptions{})
40+
if err != nil {
41+
panic(err.Error())
42+
}
43+
replicas, _ := strconv.Atoi(deployment.Annotations["k8s.dev.scaler/desired.replicas"])
44+
return int32(replicas), nil
45+
}
46+
47+
// ScaleDeploymentResources will scale all deployments up or down in a namespace
48+
func (ds *DeploymentsScaler) ScaleDeploymentResources() {
49+
resources, err := ds.client.List(context.TODO(), metav1.ListOptions{})
50+
if err != nil {
51+
panic(err)
52+
}
53+
for _, r := range resources.Items {
54+
// store original desired number of replicas as an annotation
55+
if ds.scale == "down" {
56+
err = ds.annotateResource(r.Name, *r.Spec.Replicas)
57+
if err != nil {
58+
panic(err.Error())
59+
}
60+
}
61+
// If scaling up, get the replicas from the previously stored annotation
62+
replicas := int32(0)
63+
if ds.scale == "up" {
64+
replicas, err = ds.getDesiredReplicas(r.Name)
65+
if err != nil {
66+
panic(err.Error())
67+
}
68+
}
69+
fmt.Printf(" * Scaling %s (%d to %d replicas)\n", r.Name, *r.Spec.Replicas, replicas)
70+
opts, err := ds.client.GetScale(context.TODO(), r.Name, metav1.GetOptions{})
71+
if err != nil {
72+
panic(err)
73+
}
74+
opts.Spec.Replicas = replicas
75+
ds.client.UpdateScale(context.TODO(), r.Name, opts, metav1.UpdateOptions{})
76+
}
77+
}

scaler/statefulSets.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package scaler
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
"k8s.io/apimachinery/pkg/types"
10+
"k8s.io/client-go/kubernetes"
11+
v1 "k8s.io/client-go/kubernetes/typed/apps/v1"
12+
)
13+
14+
// StatefulSetsScaler allows to scale up or down all statefulSets
15+
type StatefulSetsScaler struct {
16+
17+
// defining struct variables
18+
client v1.StatefulSetInterface
19+
namespace string
20+
scale string
21+
}
22+
23+
// NewStatefulSetsScaler instantiates
24+
func NewStatefulSetsScaler(client kubernetes.Interface, namespace string, scale string) *StatefulSetsScaler {
25+
p := new(StatefulSetsScaler)
26+
p.client = client.AppsV1().StatefulSets(namespace)
27+
p.namespace = namespace
28+
p.scale = scale
29+
return p
30+
}
31+
32+
// annotateResource places the k8s.dev.scaler/desired.replicas annotation
33+
func (ss *StatefulSetsScaler) annotateResource(name string, replicas int32) error {
34+
payload := fmt.Sprintf(`{"metadata":{"annotations":{"k8s.dev.scaler/desired.replicas":"%d"}}}`, replicas)
35+
_, err := ss.client.Patch(context.TODO(), name, types.MergePatchType, []byte(payload), metav1.PatchOptions{})
36+
return err
37+
}
38+
39+
// getDesiredReplicas fetches the value from the k8s.dev.scaler/desired.replicas annotation
40+
func (ss *StatefulSetsScaler) getDesiredReplicas(name string) (int32, error) {
41+
statefulSet, err := ss.client.Get(context.TODO(), name, metav1.GetOptions{})
42+
if err != nil {
43+
panic(err.Error())
44+
}
45+
replicas, _ := strconv.Atoi(statefulSet.Annotations["k8s.dev.scaler/desired.replicas"])
46+
return int32(replicas), nil
47+
}
48+
49+
// ScaleStatefulSetResources will scale all deployments up or down in a namespace
50+
func (ss *StatefulSetsScaler) ScaleStatefulSetResources() {
51+
resources, err := ss.client.List(context.TODO(), metav1.ListOptions{})
52+
if err != nil {
53+
panic(err)
54+
}
55+
for _, r := range resources.Items {
56+
// store original desired number of replicas as an annotation
57+
if ss.scale == "down" {
58+
err = ss.annotateResource(r.Name, *r.Spec.Replicas)
59+
if err != nil {
60+
panic(err.Error())
61+
}
62+
}
63+
// If scaling up, get the replicas from the previously stored annotation
64+
replicas := int32(0)
65+
if ss.scale == "up" {
66+
replicas, err = ss.getDesiredReplicas(r.Name)
67+
if err != nil {
68+
panic(err.Error())
69+
}
70+
}
71+
fmt.Printf(" * Scaling %s (%d to %d replicas)\n", r.Name, *r.Spec.Replicas, replicas)
72+
opts, err := ss.client.GetScale(context.TODO(), r.Name, metav1.GetOptions{})
73+
if err != nil {
74+
panic(err)
75+
}
76+
opts.Spec.Replicas = replicas
77+
ss.client.UpdateScale(context.TODO(), r.Name, opts, metav1.UpdateOptions{})
78+
}
79+
}

0 commit comments

Comments
 (0)