From f3ed290dba0660283ff938c894dc1f24cf08cf8a Mon Sep 17 00:00:00 2001 From: Zach Loafman Date: Wed, 18 Oct 2023 12:46:14 -0700 Subject: [PATCH] simple-game-server: Adds a graceful termination delay (#3436) * simple-game-server: Adds a graceful termination delay * Adds `gracefulTerminationDelaySec` to imitate a gameserver delaying after SIGTERM (eviction). * Adds test/eviction/evictpod.go, which I used to test the above. This should allow easier testing in autoscaling scenarios (including eventually possibly e2e tests with eviction: safe: Always). --- examples/simple-game-server/Makefile | 4 +- examples/simple-game-server/go.mod | 4 +- examples/simple-game-server/go.sum | 8 ++-- examples/simple-game-server/main.go | 3 ++ test/eviction/evictpod.go | 66 ++++++++++++++++++++++++++++ 5 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 test/eviction/evictpod.go diff --git a/examples/simple-game-server/Makefile b/examples/simple-game-server/Makefile index 02a41192e7..55b47c4195 100644 --- a/examples/simple-game-server/Makefile +++ b/examples/simple-game-server/Makefile @@ -39,9 +39,9 @@ WITH_ARM64 ?= 1 mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) project_path := $(dir $(mkfile_path)) ifeq ($(REPOSITORY),) - server_tag := simple-game-server:0.18 + server_tag := simple-game-server:0.19 else - server_tag := $(REPOSITORY)/simple-game-server:0.18 + server_tag := $(REPOSITORY)/simple-game-server:0.19 endif ifeq ($(WITH_WINDOWS), 1) diff --git a/examples/simple-game-server/go.mod b/examples/simple-game-server/go.mod index 206aad823b..dfc8e4b882 100644 --- a/examples/simple-game-server/go.mod +++ b/examples/simple-game-server/go.mod @@ -8,8 +8,8 @@ require ( github.com/golang/protobuf v1.5.3 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.17.1 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect diff --git a/examples/simple-game-server/go.sum b/examples/simple-game-server/go.sum index 36e6f68587..a5f943da5c 100644 --- a/examples/simple-game-server/go.sum +++ b/examples/simple-game-server/go.sum @@ -11,10 +11,10 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/examples/simple-game-server/main.go b/examples/simple-game-server/main.go index a6a496a068..bb1447f82d 100644 --- a/examples/simple-game-server/main.go +++ b/examples/simple-game-server/main.go @@ -45,6 +45,7 @@ func main() { shutdownDelaySec := flag.Int("automaticShutdownDelaySec", 0, "If greater than zero, automatically shut down the server this many seconds after the server becomes allocated (cannot be used if automaticShutdownDelayMin is set)") readyDelaySec := flag.Int("readyDelaySec", 0, "If greater than zero, wait this many seconds each time before marking the game server as ready") readyIterations := flag.Int("readyIterations", 0, "If greater than zero, return to a ready state this number of times before shutting down") + gracefulTerminationDelaySec := flag.Int("gracefulTerminationDelaySec", 0, "Delay after we've been asked to terminate (by SIGKILL or automaticShutdownDelaySec)") udp := flag.Bool("udp", true, "Server will listen on UDP") tcp := flag.Bool("tcp", false, "Server will listen on TCP") @@ -122,6 +123,8 @@ func main() { } <-sigCtx.Done() + log.Printf("Waiting %d seconds before exiting", *gracefulTerminationDelaySec) + time.Sleep(time.Duration(*gracefulTerminationDelaySec) * time.Second) os.Exit(0) } diff --git a/test/eviction/evictpod.go b/test/eviction/evictpod.go new file mode 100644 index 0000000000..cb9ad1e327 --- /dev/null +++ b/test/eviction/evictpod.go @@ -0,0 +1,66 @@ +// Copyright 2023 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +// evictpod.go --pod --namespace initiates a pod eviction +// using the k8s eviction API. +package main + +import ( + "context" + "flag" + "log" + "path/filepath" + + policy "k8s.io/api/policy/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" +) + +// Borrowed from https://stackoverflow.com/questions/62803041/how-to-evict-or-delete-pods-from-kubernetes-using-golang-client +func evictPod(ctx context.Context, client *kubernetes.Clientset, name, namespace string) error { + return client.PolicyV1().Evictions(namespace).Evict(ctx, &policy.Eviction{ + ObjectMeta: meta_v1.ObjectMeta{ + Name: name, + Namespace: namespace, + }}) +} + +func main() { + ctx := context.Background() + + kubeconfig := flag.String("kubeconfig", filepath.Join(homedir.HomeDir(), ".kube", "config"), "(optional) absolute path to the kubeconfig file") + namespace := flag.String("namespace", "default", "Namespace (defaults to `default`)") + pod := flag.String("pod", "", "Pod name (required)") + flag.Parse() + + if *pod == "" { + log.Fatal("--pod must be non-empty") + } + + config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) + if err != nil { + log.Fatalf("Could not build config: %v", err) + } + + kubeClient, err := kubernetes.NewForConfig(config) + if err != nil { + log.Fatalf("Could not create the kubernetes clientset: %v", err) + } + + if err := evictPod(ctx, kubeClient, *pod, *namespace); err != nil { + log.Fatalf("Pod eviction failed: %v", err) + } +}