Skip to content

Commit

Permalink
Implement tortoisectl stop command
Browse files Browse the repository at this point in the history
  • Loading branch information
sanposhiho committed Apr 26, 2024
1 parent 097a5c9 commit 3368e1e
Show file tree
Hide file tree
Showing 15 changed files with 1,093 additions and 33 deletions.
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ test-debug: envtest ginkgo
test-update: envtest ginkgo
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" UPDATE_TESTCASES=true $(GINKGO) -r --fail-fast

.PHONY: test-tortoisectl
test-tortoisectl: envtest
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test -timeout 30s -run Test_TortoiseCtlStop ./cmd/tortoisectl/test/... --update

GINKGO ?= $(LOCALBIN)/ginkgo
GINKGO_VERSION ?= v2.1.4

Expand All @@ -82,6 +86,10 @@ $(GINKGO): $(LOCALBIN)
build: generate fmt vet ## Build manager binary.
go build -o bin/manager main.go

.PHONY: build-tortoisectl
build-tortoisectl:
go build -o bin/tortoisectl cmd/tortoisectl/main.go

.PHONY: run
run: manifests generate fmt vet ## Run a controller from your host.
go run ./main.go
Expand Down
2 changes: 1 addition & 1 deletion api/core/v1/pod_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (h *PodWebhook) Default(ctx context.Context, obj runtime.Object) error {
return nil
}

h.podService.ModifyPodResource(pod, tortoise)
h.podService.ModifyPodSpecResource(&pod.Spec, tortoise)
pod.Annotations[annotation.PodMutationAnnotation] = fmt.Sprintf("this pod is mutated by tortoise (%s)", tortoise.Name)

return nil
Expand Down
21 changes: 21 additions & 0 deletions cmd/tortoisectl/commands/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package commands

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

var rootCmd = &cobra.Command{
Use: "tortoisectl [COMMANDS]",
Short: "tortoisectl is a CLI for managing Tortoise",
Long: `tortoisectl is a CLI for managing Tortoise.`,
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
91 changes: 91 additions & 0 deletions cmd/tortoisectl/commands/stop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package commands

import (
"fmt"
"os"
"path/filepath"

"github.com/mercari/tortoise/pkg/deployment"
"github.com/mercari/tortoise/pkg/pod"
"github.com/mercari/tortoise/pkg/stoper"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var stopCmd = &cobra.Command{
Use: "stop tortoise1 tortoise2...",
Short: "stop tortoise(s) safely",
Long: `stop is the command to turn off tortoise(s) safely.`,
RunE: func(cmd *cobra.Command, args []string) error {
// validation
if !stopAll {
if len(args) != 0 {
return fmt.Errorf("tortoise name shouldn't be specified because of --all flag")
}
} else {
if stopNamespace == "" {
return fmt.Errorf("namespace must be specified")
}
if len(args) == 0 {
return fmt.Errorf("tortoise name must be specified")
}
}

config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return fmt.Errorf("failed to build config: %v", err)
}

client, err := client.New(config, client.Options{})
if err != nil {
return fmt.Errorf("failed to create client: %v", err)
}

deploymentService := deployment.New(client, "", "", nil)
podService, err := pod.New(map[string]int64{}, "", nil, nil)
if err != nil {
return fmt.Errorf("failed to create pod service: %v", err)
}

stoperService := stoper.New(client, deploymentService, podService)

err = stoperService.Stop(cmd.Context(), args, stopNamespace, stopAll, os.Stdout, stoper.NoLoweringResource)
if err != nil {
return fmt.Errorf("failed to stop tortoise(s): %v", err)
}

return nil
},
}

var (
// namespace to stop tortoise(s) in
stopNamespace string
// stop all tortoises in the specified namespace, or in all namespaces if no namespace is specified.
stopAll bool
// Stop tortoise without lowering resource requests.
// If this flag is specified and the current Deployment's resource request(s) is lower than the current Pods' request mutated by Tortoise,
// this CLI patches the deployment so that changing tortoise to Off won't result in lowering the resource request(s), damaging the service.
noLoweringResource bool

// Path to KUBECONFIG
kubeconfig string
)

func init() {
rootCmd.AddCommand(stopCmd)

if home := homedir.HomeDir(); home != "" {
stopCmd.Flags().StringVar(&kubeconfig, "kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
stopCmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file")
}

stopCmd.Flags().StringVarP(&stopNamespace, "namespace", "n", "", "namespace to stop tortoise(s) in")
stopCmd.Flags().BoolVarP(&stopAll, "all", "A", false, "stop all tortoises in the specified namespace, or in all namespaces if no namespace is specified.")
stopCmd.Flags().BoolVar(&noLoweringResource, "no-lowering-resource", false, `Stop tortoise without lowering resource requests.
If this flag is specified and the current Deployment's resource request(s) is lower than the current Pods' request mutated by Tortoise,
this CLI patches the deployment so that changing tortoise to Off won't result in lowering the resource request(s), damaging the service.`)
}
7 changes: 7 additions & 0 deletions cmd/tortoisectl/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/mercari/tortoise/cmd/tortoisectl/commands"

func main() {
commands.Execute()
}
48 changes: 48 additions & 0 deletions cmd/tortoisectl/test/testdata/success/after/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
metadata:
name: mercari-app
namespace: test-0
spec:
progressDeadlineSeconds: 600
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
app: mercari
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
template:
metadata:
annotations:
kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z"
creationTimestamp: null
labels:
app: mercari
spec:
containers:
- image: awesome-mercari-app-image
imagePullPolicy: Always
name: app
resources:
requests:
cpu: "10"
memory: 10Gi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
- image: awesome-istio-proxy-image
imagePullPolicy: Always
name: istio-proxy
resources:
requests:
cpu: "4"
memory: 4Gi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
status: {}
143 changes: 143 additions & 0 deletions cmd/tortoisectl/test/testdata/success/after/tortoise.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
metadata:
finalizers:
- tortoise.autoscaling.mercari.com/finalizer
name: mercari
namespace: test-0
spec:
targetRefs:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mercari-app
updateMode: Auto
status:
autoscalingPolicy:
- containerName: app
policy:
cpu: Horizontal
memory: Vertical
- containerName: istio-proxy
policy:
cpu: Horizontal
memory: Vertical
conditions:
containerRecommendationFromVPA:
- containerName: app
maxRecommendation:
cpu:
quantity: "3"
updatedAt: "2023-01-01T00:00:00Z"
memory:
quantity: 3Gi
updatedAt: "2023-01-01T00:00:00Z"
recommendation:
cpu:
quantity: "3"
updatedAt: "2023-01-01T00:00:00Z"
memory:
quantity: 3Gi
updatedAt: "2023-01-01T00:00:00Z"
- containerName: istio-proxy
maxRecommendation:
cpu:
quantity: "3"
updatedAt: "2023-01-01T00:00:00Z"
memory:
quantity: 3Gi
updatedAt: "2023-01-01T00:00:00Z"
recommendation:
cpu:
quantity: "3"
updatedAt: "2023-01-01T00:00:00Z"
memory:
quantity: 3Gi
updatedAt: "2023-01-01T00:00:00Z"
containerResourceRequests:
- containerName: app
resource:
cpu: "6"
memory: 3Gi
- containerName: istio-proxy
resource:
cpu: "4"
memory: 3Gi
tortoiseConditions:
- lastTransitionTime: "2023-01-01T00:00:00Z"
lastUpdateTime: "2023-01-01T00:00:00Z"
message: the current number of replicas is not bigger than the preferred max
replica number
reason: ScaledUpBasedOnPreferredMaxReplicas
status: "False"
type: ScaledUpBasedOnPreferredMaxReplicas
- lastTransitionTime: "2023-01-01T00:00:00Z"
lastUpdateTime: "2023-01-01T00:00:00Z"
message: HPA target utilization is updated
reason: HPATargetUtilizationUpdated
status: "True"
type: HPATargetUtilizationUpdated
- lastTransitionTime: "2023-01-01T00:00:00Z"
lastUpdateTime: "2023-01-01T00:00:00Z"
message: The recommendation is provided
status: "True"
type: VerticalRecommendationUpdated
- lastTransitionTime: "2023-01-01T00:00:00Z"
lastUpdateTime: "2023-01-01T00:00:00Z"
status: "False"
type: FailedToReconcile
containerResourcePhases:
- containerName: app
resourcePhases:
cpu:
lastTransitionTime: null
phase: Working
memory:
lastTransitionTime: "2023-01-01T00:00:00Z"
phase: Working
- containerName: istio-proxy
resourcePhases:
cpu:
lastTransitionTime: null
phase: Working
memory:
lastTransitionTime: "2023-01-01T00:00:00Z"
phase: Working
recommendations:
horizontal:
maxReplicas:
- from: 0
timezone: Local
to: 24
updatedAt: "2023-01-01T00:00:00Z"
value: 20
minReplicas:
- from: 0
timezone: Local
to: 24
updatedAt: "2023-01-01T00:00:00Z"
value: 5
targetUtilizations:
- containerName: app
targetUtilization:
cpu: 50
- containerName: istio-proxy
targetUtilization:
cpu: 75
vertical:
containerResourceRecommendation:
- RecommendedResource:
cpu: "6"
memory: 3Gi
containerName: app
- RecommendedResource:
cpu: "4"
memory: 3Gi
containerName: istio-proxy
targets:
horizontalPodAutoscaler: tortoise-hpa-mercari
scaleTargetRef:
kind: ""
name: ""
verticalPodAutoscalers:
- name: tortoise-monitor-mercari
role: Monitor
tortoisePhase: Working
30 changes: 30 additions & 0 deletions cmd/tortoisectl/test/testdata/success/before/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
metadata:
name: mercari-app
namespace: default
spec:
selector:
matchLabels:
app: mercari
strategy: {}
template:
metadata:
annotations:
kubectl.kubernetes.io/restartedAt: "2023-01-01T00:00:00Z"
creationTimestamp: null
labels:
app: mercari
spec:
containers:
- image: awesome-mercari-app-image
name: app
resources:
requests:
cpu: "10"
memory: 10Gi
- image: awesome-istio-proxy-image
name: istio-proxy
resources:
requests:
cpu: "4"
memory: 4Gi
status: {}
Loading

0 comments on commit 3368e1e

Please sign in to comment.