agones.dev/agones@v1.53.0/cmd/processor/main.go (about) 1 // Copyright 2025 Google LLC All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Processor 16 package main 17 18 import ( 19 "context" 20 "os" 21 "strings" 22 "time" 23 24 "agones.dev/agones/pkg" 25 "agones.dev/agones/pkg/util/httpserver" 26 "agones.dev/agones/pkg/util/runtime" 27 "agones.dev/agones/pkg/util/signals" 28 29 "github.com/google/uuid" 30 "github.com/heptiolabs/healthcheck" 31 "github.com/sirupsen/logrus" 32 "github.com/spf13/pflag" 33 "github.com/spf13/viper" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/client-go/kubernetes" 36 "k8s.io/client-go/rest" 37 leaderelection "k8s.io/client-go/tools/leaderelection" 38 "k8s.io/client-go/tools/leaderelection/resourcelock" 39 ) 40 41 const ( 42 logLevelFlag = "log-level" 43 leaderElectionFlag = "leader-election" 44 podNamespace = "pod-namespace" 45 leaseDurationFlag = "lease-duration" 46 renewDeadlineFlag = "renew-deadline" 47 retryPeriodFlag = "retry-period" 48 ) 49 50 var ( 51 logger = runtime.NewLoggerWithSource("main") 52 ) 53 54 type processorConfig struct { 55 LogLevel string 56 LeaderElection bool 57 PodNamespace string 58 LeaseDuration time.Duration 59 RenewDeadline time.Duration 60 RetryPeriod time.Duration 61 } 62 63 func parseEnvFlags() processorConfig { 64 viper.SetDefault(logLevelFlag, "Info") 65 viper.SetDefault(leaderElectionFlag, false) 66 viper.SetDefault(podNamespace, "") 67 viper.SetDefault(leaseDurationFlag, 15*time.Second) 68 viper.SetDefault(renewDeadlineFlag, 10*time.Second) 69 viper.SetDefault(retryPeriodFlag, 2*time.Second) 70 71 pflag.String(logLevelFlag, viper.GetString(logLevelFlag), "Log level") 72 pflag.Bool(leaderElectionFlag, viper.GetBool(leaderElectionFlag), "Enable leader election") 73 pflag.String(podNamespace, viper.GetString(podNamespace), "Pod namespace") 74 pflag.Duration(leaseDurationFlag, viper.GetDuration(leaseDurationFlag), "Leader election lease duration") 75 pflag.Duration(renewDeadlineFlag, viper.GetDuration(renewDeadlineFlag), "Leader election renew deadline") 76 pflag.Duration(retryPeriodFlag, viper.GetDuration(retryPeriodFlag), "Leader election retry period") 77 pflag.Parse() 78 79 viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) 80 viper.AutomaticEnv() 81 _ = viper.BindPFlags(pflag.CommandLine) 82 83 return processorConfig{ 84 LogLevel: viper.GetString(logLevelFlag), 85 LeaderElection: viper.GetBool(leaderElectionFlag), 86 PodNamespace: viper.GetString(podNamespace), 87 LeaseDuration: viper.GetDuration(leaseDurationFlag), 88 RenewDeadline: viper.GetDuration(renewDeadlineFlag), 89 RetryPeriod: viper.GetDuration(retryPeriodFlag), 90 } 91 } 92 93 func main() { 94 ctx, cancelCtx := context.WithCancel(context.Background()) 95 defer cancelCtx() 96 97 conf := parseEnvFlags() 98 99 logger.WithField("version", pkg.Version).WithField("processorConf", conf). 100 WithField("featureGates", runtime.EncodeFeatures()). 101 Info("Starting agones-processor") 102 103 logger.WithField("logLevel", conf.LogLevel).Info("Setting LogLevel configuration") 104 level, err := logrus.ParseLevel(strings.ToLower(conf.LogLevel)) 105 if err == nil { 106 runtime.SetLevel(level) 107 } else { 108 logger.WithError(err).Info("Specified wrong Logging. Setting default loglevel - Info") 109 runtime.SetLevel(logrus.InfoLevel) 110 } 111 112 healthserver := &httpserver.Server{Logger: logger} 113 health := healthcheck.NewHandler() 114 115 config, err := rest.InClusterConfig() 116 if err != nil { 117 logger.WithError(err).Fatal("Failed to create in-cluster config") 118 panic("Failed to create in-cluster config: " + err.Error()) 119 } 120 kubeClient, err := kubernetes.NewForConfig(config) 121 if err != nil { 122 logger.WithError(err).Fatal("Failed to create Kubernetes client") 123 panic("Failed to create Kubernetes client: " + err.Error()) 124 } 125 126 go func() { 127 healthserver.Handle("/", health) 128 _ = healthserver.Run(context.Background(), 0) 129 }() 130 131 signals.NewSigTermHandler(func() { 132 logger.Info("Pod shutdown has been requested, failing readiness check") 133 cancelCtx() 134 os.Exit(0) 135 }) 136 137 whenLeader(ctx, cancelCtx, logger, conf, kubeClient, func(ctx context.Context) { 138 logger.Info("Starting processor work as leader") 139 140 // Simulate processor work (to ensure the leader is working) 141 // TODO: implement processor work 142 ticker := time.NewTicker(5 * time.Second) 143 defer ticker.Stop() 144 145 for { 146 select { 147 case <-ctx.Done(): 148 logger.Info("Processor work completed") 149 return 150 case <-ticker.C: 151 logger.Info("Processor is active as leader") 152 } 153 } 154 }) 155 156 logger.Info("Processor exited gracefully.") 157 } 158 159 func whenLeader(ctx context.Context, cancel context.CancelFunc, logger *logrus.Entry, 160 conf processorConfig, kubeClient *kubernetes.Clientset, start func(_ context.Context)) { 161 if !conf.LeaderElection { 162 start(ctx) 163 return 164 } 165 166 id := uuid.New().String() 167 168 lock := &resourcelock.LeaseLock{ 169 LeaseMeta: metav1.ObjectMeta{ 170 Name: "agones-processor-lock", 171 Namespace: conf.PodNamespace, 172 }, 173 Client: kubeClient.CoordinationV1(), 174 LockConfig: resourcelock.ResourceLockConfig{ 175 Identity: id, 176 }, 177 } 178 179 logger.WithField("id", id).Info("Leader Election ID") 180 181 leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ 182 Lock: lock, 183 // IMPORTANT: you MUST ensure that any code you have that 184 // is protected by the lease must terminate **before** 185 // you call cancel. Otherwise, you could have a background 186 // loop still running and another process could 187 // get elected before your background loop finished, violating 188 // the stated goal of the lease. 189 ReleaseOnCancel: true, 190 LeaseDuration: conf.LeaseDuration, 191 RenewDeadline: conf.RenewDeadline, 192 RetryPeriod: conf.RetryPeriod, 193 Callbacks: leaderelection.LeaderCallbacks{ 194 OnStartedLeading: start, 195 OnStoppedLeading: func() { 196 logger.WithField("id", id).Info("Leader Lost") 197 cancel() 198 os.Exit(0) 199 }, 200 OnNewLeader: func(identity string) { 201 if identity == id { 202 return 203 } 204 logger.WithField("id", id).Info("New Leader Elected") 205 }, 206 }, 207 }) 208 }