agones.dev/agones@v1.54.0/pkg/gameservers/controller.go (about)

     1  // Copyright 2018 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  package gameservers
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"strconv"
    22  	"sync"
    23  	"time"
    24  
    25  	"agones.dev/agones/pkg/apis/agones"
    26  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    27  	"agones.dev/agones/pkg/client/clientset/versioned"
    28  	getterv1 "agones.dev/agones/pkg/client/clientset/versioned/typed/agones/v1"
    29  	"agones.dev/agones/pkg/client/informers/externalversions"
    30  	listerv1 "agones.dev/agones/pkg/client/listers/agones/v1"
    31  	"agones.dev/agones/pkg/cloudproduct"
    32  	"agones.dev/agones/pkg/portallocator"
    33  	"agones.dev/agones/pkg/util/crd"
    34  	"agones.dev/agones/pkg/util/logfields"
    35  	"agones.dev/agones/pkg/util/runtime"
    36  	"agones.dev/agones/pkg/util/webhooks"
    37  	"agones.dev/agones/pkg/util/workerqueue"
    38  	"github.com/heptiolabs/healthcheck"
    39  	"github.com/pkg/errors"
    40  	"github.com/sirupsen/logrus"
    41  	"gomodules.xyz/jsonpatch/v2"
    42  	admissionv1 "k8s.io/api/admission/v1"
    43  	corev1 "k8s.io/api/core/v1"
    44  	extclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    45  	apiextclientv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1"
    46  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    47  	"k8s.io/apimachinery/pkg/api/resource"
    48  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    49  	runtimeschema "k8s.io/apimachinery/pkg/runtime/schema"
    50  	"k8s.io/apimachinery/pkg/util/intstr"
    51  	"k8s.io/client-go/informers"
    52  	"k8s.io/client-go/kubernetes"
    53  	"k8s.io/client-go/kubernetes/scheme"
    54  	typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
    55  	corelisterv1 "k8s.io/client-go/listers/core/v1"
    56  	"k8s.io/client-go/tools/cache"
    57  	"k8s.io/client-go/tools/record"
    58  	"k8s.io/client-go/util/workqueue"
    59  	"k8s.io/utils/ptr"
    60  )
    61  
    62  const (
    63  	sdkserverSidecarName  = "agones-gameserver-sidecar"
    64  	grpcPortEnvVar        = "AGONES_SDK_GRPC_PORT"
    65  	httpPortEnvVar        = "AGONES_SDK_HTTP_PORT"
    66  	passthroughPortEnvVar = "PASSTHROUGH"
    67  )
    68  
    69  // Extensions struct contains what is needed to bind webhook handlers
    70  type Extensions struct {
    71  	baseLogger *logrus.Entry
    72  	apiHooks   agonesv1.APIHooks
    73  }
    74  
    75  // Controller is a the main GameServer crd controller
    76  //
    77  //nolint:govet // ignore fieldalignment, singleton
    78  type Controller struct {
    79  	baseLogger               *logrus.Entry
    80  	controllerHooks          cloudproduct.ControllerHooksInterface
    81  	sidecarImage             string
    82  	alwaysPullSidecarImage   bool
    83  	sidecarCPURequest        resource.Quantity
    84  	sidecarCPULimit          resource.Quantity
    85  	sidecarMemoryRequest     resource.Quantity
    86  	sidecarMemoryLimit       resource.Quantity
    87  	sidecarRunAsUser         int
    88  	sidecarRequestsRateLimit time.Duration
    89  	sdkServiceAccount        string
    90  	crdGetter                apiextclientv1.CustomResourceDefinitionInterface
    91  	podGetter                typedcorev1.PodsGetter
    92  	podLister                corelisterv1.PodLister
    93  	podSynced                cache.InformerSynced
    94  	gameServerGetter         getterv1.GameServersGetter
    95  	gameServerLister         listerv1.GameServerLister
    96  	gameServerSynced         cache.InformerSynced
    97  	nodeLister               corelisterv1.NodeLister
    98  	nodeSynced               cache.InformerSynced
    99  	portAllocator            portallocator.Interface
   100  	healthController         *HealthController
   101  	migrationController      *MigrationController
   102  	missingPodController     *MissingPodController
   103  	succeededController      *SucceededController
   104  	workerqueue              *workerqueue.WorkerQueue
   105  	creationWorkerQueue      *workerqueue.WorkerQueue // handles creation only
   106  	deletionWorkerQueue      *workerqueue.WorkerQueue // handles deletion only
   107  	recorder                 record.EventRecorder
   108  }
   109  
   110  // NewController returns a new gameserver crd controller
   111  func NewController(
   112  	controllerHooks cloudproduct.ControllerHooksInterface,
   113  	health healthcheck.Handler,
   114  	portRanges map[string]portallocator.PortRange,
   115  	sidecarImage string,
   116  	alwaysPullSidecarImage bool,
   117  	sidecarCPURequest resource.Quantity,
   118  	sidecarCPULimit resource.Quantity,
   119  	sidecarMemoryRequest resource.Quantity,
   120  	sidecarMemoryLimit resource.Quantity,
   121  	sidecarRunAsUser int,
   122  	sidecarRequestsRateLimit time.Duration,
   123  	sdkServiceAccount string,
   124  	kubeClient kubernetes.Interface,
   125  	kubeInformerFactory informers.SharedInformerFactory,
   126  	extClient extclientset.Interface,
   127  	agonesClient versioned.Interface,
   128  	agonesInformerFactory externalversions.SharedInformerFactory,
   129  ) *Controller {
   130  
   131  	pods := kubeInformerFactory.Core().V1().Pods()
   132  	gameServers := agonesInformerFactory.Agones().V1().GameServers()
   133  	gsInformer := gameServers.Informer()
   134  
   135  	c := &Controller{
   136  		controllerHooks:          controllerHooks,
   137  		sidecarImage:             sidecarImage,
   138  		sidecarCPULimit:          sidecarCPULimit,
   139  		sidecarCPURequest:        sidecarCPURequest,
   140  		sidecarMemoryLimit:       sidecarMemoryLimit,
   141  		sidecarMemoryRequest:     sidecarMemoryRequest,
   142  		sidecarRunAsUser:         sidecarRunAsUser,
   143  		sidecarRequestsRateLimit: sidecarRequestsRateLimit,
   144  		alwaysPullSidecarImage:   alwaysPullSidecarImage,
   145  		sdkServiceAccount:        sdkServiceAccount,
   146  		crdGetter:                extClient.ApiextensionsV1().CustomResourceDefinitions(),
   147  		podGetter:                kubeClient.CoreV1(),
   148  		podLister:                pods.Lister(),
   149  		podSynced:                pods.Informer().HasSynced,
   150  		gameServerGetter:         agonesClient.AgonesV1(),
   151  		gameServerLister:         gameServers.Lister(),
   152  		gameServerSynced:         gsInformer.HasSynced,
   153  		nodeLister:               kubeInformerFactory.Core().V1().Nodes().Lister(),
   154  		nodeSynced:               kubeInformerFactory.Core().V1().Nodes().Informer().HasSynced,
   155  		portAllocator:            controllerHooks.NewPortAllocator(portRanges, kubeInformerFactory, agonesInformerFactory),
   156  		healthController:         NewHealthController(health, kubeClient, agonesClient, kubeInformerFactory, agonesInformerFactory, controllerHooks.WaitOnFreePorts()),
   157  		migrationController:      NewMigrationController(health, kubeClient, agonesClient, kubeInformerFactory, agonesInformerFactory, controllerHooks.SyncPodPortsToGameServer),
   158  		missingPodController:     NewMissingPodController(health, kubeClient, agonesClient, kubeInformerFactory, agonesInformerFactory),
   159  		succeededController:      NewSucceededController(health, kubeClient, agonesClient, kubeInformerFactory, agonesInformerFactory),
   160  	}
   161  
   162  	c.baseLogger = runtime.NewLoggerWithType(c)
   163  
   164  	eventBroadcaster := record.NewBroadcaster()
   165  	eventBroadcaster.StartLogging(c.baseLogger.Debugf)
   166  	eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")})
   167  	c.recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "gameserver-controller"})
   168  
   169  	c.workerqueue = workerqueue.NewWorkerQueueWithRateLimiter(c.syncGameServer, c.baseLogger, logfields.GameServerKey, agones.GroupName+".GameServerController", fastRateLimiter())
   170  	c.creationWorkerQueue = workerqueue.NewWorkerQueueWithRateLimiter(c.syncGameServer, c.baseLogger.WithField("subqueue", "creation"), logfields.GameServerKey, agones.GroupName+".GameServerControllerCreation", fastRateLimiter())
   171  	c.deletionWorkerQueue = workerqueue.NewWorkerQueueWithRateLimiter(c.syncGameServer, c.baseLogger.WithField("subqueue", "deletion"), logfields.GameServerKey, agones.GroupName+".GameServerControllerDeletion", fastRateLimiter())
   172  	health.AddLivenessCheck("gameserver-workerqueue", healthcheck.Check(c.workerqueue.Healthy))
   173  	health.AddLivenessCheck("gameserver-creation-workerqueue", healthcheck.Check(c.creationWorkerQueue.Healthy))
   174  	health.AddLivenessCheck("gameserver-deletion-workerqueue", healthcheck.Check(c.deletionWorkerQueue.Healthy))
   175  
   176  	_, _ = gsInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
   177  		AddFunc: c.enqueueGameServerBasedOnState,
   178  		UpdateFunc: func(oldObj, newObj interface{}) {
   179  			// no point in processing unless there is a State change
   180  			oldGs := oldObj.(*agonesv1.GameServer)
   181  			newGs := newObj.(*agonesv1.GameServer)
   182  			if oldGs.Status.State != newGs.Status.State || !newGs.ObjectMeta.DeletionTimestamp.IsZero() {
   183  				c.enqueueGameServerBasedOnState(newGs)
   184  			}
   185  		},
   186  	})
   187  
   188  	// track pod deletions, for when GameServers are deleted
   189  	_, _ = pods.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
   190  		UpdateFunc: func(oldObj, newObj interface{}) {
   191  			oldPod := oldObj.(*corev1.Pod)
   192  			if isGameServerPod(oldPod) {
   193  				newPod := newObj.(*corev1.Pod)
   194  				//  node name has changed -- i.e. it has been scheduled
   195  				if oldPod.Spec.NodeName != newPod.Spec.NodeName {
   196  					owner := metav1.GetControllerOf(newPod)
   197  					c.workerqueue.Enqueue(cache.ExplicitKey(newPod.ObjectMeta.Namespace + "/" + owner.Name))
   198  				}
   199  			}
   200  		},
   201  		DeleteFunc: func(obj interface{}) {
   202  			// Could be a DeletedFinalStateUnknown, in which case, just ignore it
   203  			pod, ok := obj.(*corev1.Pod)
   204  			if ok && isGameServerPod(pod) {
   205  				owner := metav1.GetControllerOf(pod)
   206  				c.workerqueue.Enqueue(cache.ExplicitKey(pod.ObjectMeta.Namespace + "/" + owner.Name))
   207  			}
   208  		},
   209  	})
   210  
   211  	return c
   212  }
   213  
   214  // NewExtensions binds the handlers to the webhook outside the initialization of the controller
   215  // initializes a new logger for extensions.
   216  func NewExtensions(apiHooks agonesv1.APIHooks, wh *webhooks.WebHook) *Extensions {
   217  	ext := &Extensions{apiHooks: apiHooks}
   218  
   219  	ext.baseLogger = runtime.NewLoggerWithType(ext)
   220  
   221  	wh.AddHandler("/mutate", agonesv1.Kind("GameServer"), admissionv1.Create, ext.creationMutationHandler)
   222  	wh.AddHandler("/validate", agonesv1.Kind("GameServer"), admissionv1.Create, ext.creationValidationHandler)
   223  	wh.AddHandler("/mutate", corev1.SchemeGroupVersion.WithKind("Pod").GroupKind(), admissionv1.Create, ext.creationMutationHandlerPod)
   224  
   225  	return ext
   226  }
   227  
   228  func (c *Controller) enqueueGameServerBasedOnState(item interface{}) {
   229  	gs := item.(*agonesv1.GameServer)
   230  
   231  	switch gs.Status.State {
   232  	case agonesv1.GameServerStatePortAllocation,
   233  		agonesv1.GameServerStateCreating:
   234  		c.creationWorkerQueue.Enqueue(gs)
   235  
   236  	case agonesv1.GameServerStateShutdown:
   237  		c.deletionWorkerQueue.Enqueue(gs)
   238  
   239  	default:
   240  		c.workerqueue.Enqueue(gs)
   241  	}
   242  }
   243  
   244  // fastRateLimiter returns a fast rate limiter, without exponential back-off.
   245  func fastRateLimiter() workqueue.TypedRateLimiter[any] {
   246  	const numFastRetries = 5
   247  	const fastDelay = 20 * time.Millisecond  // first few retries up to 'numFastRetries' are fast
   248  	const slowDelay = 500 * time.Millisecond // subsequent retries are slow
   249  
   250  	return workqueue.NewTypedItemFastSlowRateLimiter[any](fastDelay, slowDelay, numFastRetries)
   251  }
   252  
   253  // creationMutationHandler is the handler for the mutating webhook that sets the
   254  // the default values on the GameServer
   255  // Should only be called on gameserver create operations.
   256  // nolint:dupl
   257  func (ext *Extensions) creationMutationHandler(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error) {
   258  	obj := review.Request.Object
   259  	gs := &agonesv1.GameServer{}
   260  	err := json.Unmarshal(obj.Raw, gs)
   261  	if err != nil {
   262  		// If the JSON is invalid during mutation, fall through to validation. This allows OpenAPI schema validation
   263  		// to proceed, resulting in a more user friendly error message.
   264  		return review, nil
   265  	}
   266  
   267  	// This is the main logic of this function
   268  	// the rest is really just json plumbing
   269  	gs.ApplyDefaults()
   270  
   271  	newGS, err := json.Marshal(gs)
   272  	if err != nil {
   273  		return review, errors.Wrapf(err, "error marshalling default applied GameServer %s to json", gs.ObjectMeta.Name)
   274  	}
   275  
   276  	patch, err := jsonpatch.CreatePatch(obj.Raw, newGS)
   277  	if err != nil {
   278  		return review, errors.Wrapf(err, "error creating patch for GameServer %s", gs.ObjectMeta.Name)
   279  	}
   280  
   281  	jsonPatch, err := json.Marshal(patch)
   282  	if err != nil {
   283  		return review, errors.Wrapf(err, "error creating json for patch for GameServer %s", gs.ObjectMeta.Name)
   284  	}
   285  
   286  	pt := admissionv1.PatchTypeJSONPatch
   287  	review.Response.PatchType = &pt
   288  	review.Response.Patch = jsonPatch
   289  
   290  	return review, nil
   291  }
   292  
   293  func loggerForGameServerKey(key string, logger *logrus.Entry) *logrus.Entry {
   294  	return logfields.AugmentLogEntry(logger, logfields.GameServerKey, key)
   295  }
   296  
   297  func loggerForGameServer(gs *agonesv1.GameServer, logger *logrus.Entry) *logrus.Entry {
   298  	gsName := logfields.NilGameServer
   299  	if gs != nil {
   300  		gsName = gs.Namespace + "/" + gs.Name
   301  	}
   302  	return loggerForGameServerKey(gsName, logger).WithField("gs", gs)
   303  }
   304  
   305  // creationValidationHandler that validates a GameServer when it is created
   306  // Should only be called on gameserver create operations.
   307  func (ext *Extensions) creationValidationHandler(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error) {
   308  	obj := review.Request.Object
   309  	gs := &agonesv1.GameServer{}
   310  	err := json.Unmarshal(obj.Raw, gs)
   311  	if err != nil {
   312  		return review, errors.Wrapf(err, "error unmarshalling GameServer json after schema validation: %s", obj.Raw)
   313  	}
   314  
   315  	loggerForGameServer(gs, ext.baseLogger).WithField("review", review).Debug("creationValidationHandler")
   316  
   317  	if errs := gs.Validate(ext.apiHooks); len(errs) > 0 {
   318  		kind := runtimeschema.GroupKind{
   319  			Group: review.Request.Kind.Group,
   320  			Kind:  review.Request.Kind.Kind,
   321  		}
   322  		statusErr := k8serrors.NewInvalid(kind, review.Request.Name, errs)
   323  		review.Response.Allowed = false
   324  		review.Response.Result = &statusErr.ErrStatus
   325  		loggerForGameServer(gs, ext.baseLogger).WithField("review", review).Debug("Invalid GameServer")
   326  	}
   327  	return review, nil
   328  }
   329  
   330  // creationMutationHandlerPod that mutates a GameServer pod when it is created
   331  // Should only be called on gameserver pod create operations.
   332  func (ext *Extensions) creationMutationHandlerPod(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error) {
   333  	obj := review.Request.Object
   334  	pod := &corev1.Pod{}
   335  	err := json.Unmarshal(obj.Raw, pod)
   336  	if err != nil {
   337  		// If the JSON is invalid during mutation, fall through to validation. This allows OpenAPI schema validation
   338  		// to proceed, resulting in a more user friendly error message.
   339  		return review, nil
   340  	}
   341  
   342  	ext.baseLogger.WithField("pod.Name", pod.Name).Debug("creationMutationHandlerPod")
   343  
   344  	annotation, ok := pod.ObjectMeta.Annotations[agonesv1.PassthroughPortAssignmentAnnotation]
   345  	if !ok {
   346  		ext.baseLogger.WithField("pod.Name", pod.Name).Info("the agones.dev/container-passthrough-port-assignment annotation is empty and it's unexpected")
   347  		return review, nil
   348  	}
   349  
   350  	passthroughPortAssignmentMap := make(map[string][]int)
   351  	if err := json.Unmarshal([]byte(annotation), &passthroughPortAssignmentMap); err != nil {
   352  		return review, errors.Wrapf(err, "could not unmarshal annotation %q (value %q)", passthroughPortAssignmentMap, annotation)
   353  	}
   354  
   355  	for _, container := range pod.Spec.Containers {
   356  		for _, portIdx := range passthroughPortAssignmentMap[container.Name] {
   357  			container.Ports[portIdx].ContainerPort = container.Ports[portIdx].HostPort
   358  		}
   359  	}
   360  
   361  	newPod, err := json.Marshal(pod)
   362  	if err != nil {
   363  		return review, errors.Wrapf(err, "error marshalling changes applied Pod %s to json", pod.ObjectMeta.Name)
   364  	}
   365  
   366  	patch, err := jsonpatch.CreatePatch(obj.Raw, newPod)
   367  	if err != nil {
   368  		return review, errors.Wrapf(err, "error creating patch for Pod %s", pod.ObjectMeta.Name)
   369  	}
   370  
   371  	jsonPatch, err := json.Marshal(patch)
   372  	if err != nil {
   373  		return review, errors.Wrapf(err, "error creating json for patch for Pod %s", pod.ObjectMeta.Name)
   374  	}
   375  
   376  	pt := admissionv1.PatchTypeJSONPatch
   377  	review.Response.PatchType = &pt
   378  	review.Response.Patch = jsonPatch
   379  
   380  	return review, nil
   381  }
   382  
   383  // Run the GameServer controller. Will block until stop is closed.
   384  // Runs threadiness number workers to process the rate limited queue
   385  func (c *Controller) Run(ctx context.Context, workers int) error {
   386  	err := crd.WaitForEstablishedCRD(ctx, c.crdGetter, "gameservers.agones.dev", c.baseLogger)
   387  	if err != nil {
   388  		return err
   389  	}
   390  
   391  	c.baseLogger.Debug("Wait for cache sync")
   392  	if !cache.WaitForCacheSync(ctx.Done(), c.gameServerSynced, c.podSynced, c.nodeSynced) {
   393  		return errors.New("failed to wait for caches to sync")
   394  	}
   395  
   396  	// Run the Port Allocator
   397  	if err = c.portAllocator.Run(ctx); err != nil {
   398  		return errors.Wrap(err, "error running the port allocator")
   399  	}
   400  
   401  	// Run the Health Controller
   402  	go func() {
   403  		if err := c.healthController.Run(ctx, workers); err != nil {
   404  			c.baseLogger.WithError(err).Error("error running health controller")
   405  		}
   406  	}()
   407  
   408  	// Run the Migration Controller
   409  	go func() {
   410  		if err := c.migrationController.Run(ctx, workers); err != nil {
   411  			c.baseLogger.WithError(err).Error("error running migration controller")
   412  		}
   413  	}()
   414  
   415  	// Run the Missing Pod Controller
   416  	go func() {
   417  		if err := c.missingPodController.Run(ctx, workers); err != nil {
   418  			c.baseLogger.WithError(err).Error("error running missing pod controller")
   419  		}
   420  	}()
   421  
   422  	// Run the Succeeded Controller
   423  	if runtime.FeatureEnabled(runtime.FeatureSidecarContainers) {
   424  		go func() {
   425  			if err := c.succeededController.Run(ctx, workers); err != nil {
   426  				c.baseLogger.WithError(err).Error("error running succeeded controller")
   427  			}
   428  		}()
   429  	}
   430  
   431  	// start work queues
   432  	var wg sync.WaitGroup
   433  
   434  	startWorkQueue := func(wq *workerqueue.WorkerQueue) {
   435  		wg.Add(1)
   436  		go func() {
   437  			defer wg.Done()
   438  			wq.Run(ctx, workers)
   439  		}()
   440  	}
   441  
   442  	startWorkQueue(c.workerqueue)
   443  	startWorkQueue(c.creationWorkerQueue)
   444  	startWorkQueue(c.deletionWorkerQueue)
   445  	wg.Wait()
   446  	return nil
   447  }
   448  
   449  // syncGameServer synchronises the Pods for the GameServers.
   450  // and reacts to status changes that can occur through the client SDK
   451  func (c *Controller) syncGameServer(ctx context.Context, key string) error {
   452  	loggerForGameServerKey(key, c.baseLogger).Debug("Synchronising")
   453  
   454  	// Convert the namespace/name string into a distinct namespace and name
   455  	namespace, name, err := cache.SplitMetaNamespaceKey(key)
   456  	if err != nil {
   457  		// don't return an error, as we don't want this retried
   458  		runtime.HandleError(loggerForGameServerKey(key, c.baseLogger), errors.Wrapf(err, "invalid resource key"))
   459  		return nil
   460  	}
   461  
   462  	gs, err := c.gameServerLister.GameServers(namespace).Get(name)
   463  	if err != nil {
   464  		if k8serrors.IsNotFound(err) {
   465  			loggerForGameServerKey(key, c.baseLogger).Debug("GameServer is no longer available for syncing")
   466  			return nil
   467  		}
   468  		return errors.Wrapf(err, "error retrieving GameServer %s from namespace %s", name, namespace)
   469  	}
   470  
   471  	if gs, err = c.syncGameServerDeletionTimestamp(ctx, gs); err != nil {
   472  		return err
   473  	}
   474  	if gs, err = c.syncGameServerPortAllocationState(ctx, gs); err != nil {
   475  		return err
   476  	}
   477  	if gs, err = c.syncGameServerCreatingState(ctx, gs); err != nil {
   478  		return err
   479  	}
   480  	if gs, err = c.syncGameServerStartingState(ctx, gs); err != nil {
   481  		return err
   482  	}
   483  	if gs, err = c.syncGameServerRequestReadyState(ctx, gs); err != nil {
   484  		return err
   485  	}
   486  	if gs, err = c.syncDevelopmentGameServer(ctx, gs); err != nil {
   487  		return err
   488  	}
   489  	if err := c.syncGameServerShutdownState(ctx, gs); err != nil {
   490  		return err
   491  	}
   492  
   493  	return nil
   494  }
   495  
   496  // syncGameServerDeletionTimestamp if the deletion timestamp is non-zero
   497  // then do one of two things:
   498  // - if the GameServer has Pods running, delete them
   499  // - if there no pods, remove the finalizer
   500  func (c *Controller) syncGameServerDeletionTimestamp(ctx context.Context, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   501  	if gs.ObjectMeta.DeletionTimestamp.IsZero() {
   502  		return gs, nil
   503  	}
   504  
   505  	loggerForGameServer(gs, c.baseLogger).Debug("Syncing with Deletion Timestamp")
   506  
   507  	pod, err := c.gameServerPod(gs)
   508  	if err != nil && !k8serrors.IsNotFound(err) {
   509  		return gs, err
   510  	}
   511  
   512  	_, isDev := gs.GetDevAddress()
   513  	if pod != nil && !isDev {
   514  		// only need to do this once
   515  		if pod.ObjectMeta.DeletionTimestamp.IsZero() {
   516  			err = c.podGetter.Pods(pod.ObjectMeta.Namespace).Delete(ctx, pod.ObjectMeta.Name, metav1.DeleteOptions{})
   517  			if err != nil {
   518  				return gs, errors.Wrapf(err, "error deleting pod for GameServer. Name: %s, Namespace: %s", gs.ObjectMeta.Name, pod.ObjectMeta.Namespace)
   519  			}
   520  			c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), fmt.Sprintf("Deleting Pod %s", pod.ObjectMeta.Name))
   521  		}
   522  
   523  		// but no removing finalizers until it's truly gone
   524  		return gs, nil
   525  	}
   526  
   527  	gsCopy := gs.DeepCopy()
   528  	// remove the finalizer for this controller
   529  	var fin []string
   530  	for _, f := range gsCopy.ObjectMeta.Finalizers {
   531  		if f != agones.GroupName && f != agonesv1.FinalizerName {
   532  			fin = append(fin, f)
   533  		}
   534  	}
   535  	gsCopy.ObjectMeta.Finalizers = fin
   536  	loggerForGameServer(gsCopy, c.baseLogger).Debugf("No pods found, removing finalizer %s", agonesv1.FinalizerName)
   537  	gs, err = c.gameServerGetter.GameServers(gsCopy.ObjectMeta.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
   538  	return gs, errors.Wrapf(err, "error removing finalizer for GameServer %s", gsCopy.ObjectMeta.Name)
   539  }
   540  
   541  // syncGameServerPortAllocationState gives a port to a dynamically allocating GameServer
   542  func (c *Controller) syncGameServerPortAllocationState(ctx context.Context, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   543  	if !(gs.Status.State == agonesv1.GameServerStatePortAllocation && gs.ObjectMeta.DeletionTimestamp.IsZero()) {
   544  		return gs, nil
   545  	}
   546  
   547  	gsCopy := c.portAllocator.Allocate(gs.DeepCopy())
   548  
   549  	gsCopy.Status.State = agonesv1.GameServerStateCreating
   550  	c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "Port allocated")
   551  
   552  	loggerForGameServer(gsCopy, c.baseLogger).Debug("Syncing Port Allocation GameServerState")
   553  	gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
   554  	if err != nil {
   555  		// if the GameServer doesn't get updated with the port data, then put the port
   556  		// back in the pool, as it will get retried on the next pass
   557  		c.portAllocator.DeAllocate(gsCopy)
   558  		return gs, errors.Wrapf(err, "error updating GameServer %s to default values", gs.Name)
   559  	}
   560  
   561  	return gs, nil
   562  }
   563  
   564  // syncGameServerCreatingState checks if the GameServer is in the Creating state, and if so
   565  // creates a Pod for the GameServer and moves the state to Starting
   566  func (c *Controller) syncGameServerCreatingState(ctx context.Context, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   567  	if !(gs.Status.State == agonesv1.GameServerStateCreating && gs.ObjectMeta.DeletionTimestamp.IsZero()) {
   568  		return gs, nil
   569  	}
   570  	if _, isDev := gs.GetDevAddress(); isDev {
   571  		return gs, nil
   572  	}
   573  
   574  	loggerForGameServer(gs, c.baseLogger).Debug("Syncing Create State")
   575  
   576  	// Maybe something went wrong, and the pod was created, but the state was never moved to Starting, so let's check
   577  	_, err := c.gameServerPod(gs)
   578  	if k8serrors.IsNotFound(err) {
   579  
   580  		for i := range gs.Spec.Ports {
   581  			if gs.Spec.Ports[i].PortPolicy == agonesv1.Static && gs.Spec.Ports[i].Protocol == agonesv1.ProtocolTCPUDP {
   582  				name := gs.Spec.Ports[i].Name
   583  				gs.Spec.Ports[i].Name = name + "-tcp"
   584  				gs.Spec.Ports[i].Protocol = corev1.ProtocolTCP
   585  
   586  				// Add separate UDP port configuration
   587  				gs.Spec.Ports = append(gs.Spec.Ports, agonesv1.GameServerPort{
   588  					PortPolicy:    agonesv1.Static,
   589  					Name:          name + "-udp",
   590  					ContainerPort: gs.Spec.Ports[i].ContainerPort,
   591  					HostPort:      gs.Spec.Ports[i].HostPort,
   592  					Protocol:      corev1.ProtocolUDP,
   593  					Container:     gs.Spec.Ports[i].Container,
   594  				})
   595  			}
   596  		}
   597  		gs, err = c.createGameServerPod(ctx, gs)
   598  		if err != nil || gs.Status.State == agonesv1.GameServerStateError {
   599  			return gs, err
   600  		}
   601  	}
   602  
   603  	if err != nil {
   604  		return nil, errors.WithStack(err)
   605  	}
   606  
   607  	gsCopy := gs.DeepCopy()
   608  	gsCopy.Status.State = agonesv1.GameServerStateStarting
   609  	gs, err = c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
   610  	if err != nil {
   611  		return gs, errors.Wrapf(err, "error updating GameServer %s to Starting state", gs.Name)
   612  	}
   613  	return gs, nil
   614  }
   615  
   616  // syncDevelopmentGameServer manages advances a development gameserver to Ready status and registers its address and ports.
   617  func (c *Controller) syncDevelopmentGameServer(ctx context.Context, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   618  	// do not sync if the server is deleting.
   619  	if !(gs.ObjectMeta.DeletionTimestamp.IsZero()) {
   620  		return gs, nil
   621  	}
   622  	// Get the development IP address
   623  	devIPAddress, isDevGs := gs.GetDevAddress()
   624  	if !isDevGs {
   625  		return gs, nil
   626  	}
   627  
   628  	// Only move from Creating -> Ready or RequestReady -> Ready.
   629  	// Shutdown -> Delete will still be handled normally by syncGameServerShutdownState.
   630  	// Other manual state changes are up to the end user.
   631  	if gs.Status.State != agonesv1.GameServerStateCreating && gs.Status.State != agonesv1.GameServerStateRequestReady {
   632  		return gs, nil
   633  	}
   634  
   635  	loggerForGameServer(gs, c.baseLogger).Debug("GS is a development game server and will not be managed by Agones.")
   636  	gsCopy := gs.DeepCopy()
   637  
   638  	gsCopy.Status.State = agonesv1.GameServerStateReady
   639  
   640  	if gs.Status.State == agonesv1.GameServerStateCreating {
   641  		var ports []agonesv1.GameServerStatusPort
   642  		for _, p := range gs.Spec.Ports {
   643  			ports = append(ports, p.Status())
   644  		}
   645  
   646  		gsCopy.Status.Ports = ports
   647  		gsCopy.Status.Address = devIPAddress
   648  		gsCopy.Status.Addresses = []corev1.NodeAddress{{Address: devIPAddress, Type: "InternalIP"}}
   649  		gsCopy.Status.NodeName = devIPAddress
   650  	}
   651  
   652  	gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
   653  	if err != nil {
   654  		return gs, errors.Wrapf(err, "error updating GameServer %s to %v status", gs.Name, gs.Status)
   655  	}
   656  	return gs, nil
   657  }
   658  
   659  // createGameServerPod creates the backing Pod for a given GameServer
   660  func (c *Controller) createGameServerPod(ctx context.Context, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   661  	sidecar := c.sidecar(gs)
   662  	pod, err := gs.Pod(c.controllerHooks, sidecar)
   663  	if err != nil {
   664  		// this shouldn't happen, but if it does.
   665  		loggerForGameServer(gs, c.baseLogger).WithError(err).Error("error creating pod from Game Server")
   666  		gs, err = c.moveToErrorState(ctx, gs, err.Error())
   667  		return gs, err
   668  	}
   669  
   670  	// if the service account is not set, then you are in the "opinionated"
   671  	// mode. If the user sets the service account, we assume they know what they are
   672  	// doing, and don't disable the gameserver container.
   673  	if pod.Spec.ServiceAccountName == "" {
   674  		pod.Spec.ServiceAccountName = c.sdkServiceAccount
   675  		err = gs.DisableServiceAccount(pod)
   676  		if err != nil {
   677  			return gs, err
   678  		}
   679  	}
   680  
   681  	err = c.addGameServerHealthCheck(gs, pod)
   682  	if err != nil {
   683  		return gs, err
   684  	}
   685  	c.addSDKServerEnvVars(gs, pod)
   686  
   687  	loggerForGameServer(gs, c.baseLogger).WithField("pod", pod).Debug("Creating Pod for GameServer")
   688  	pod, err = c.podGetter.Pods(gs.ObjectMeta.Namespace).Create(ctx, pod, metav1.CreateOptions{})
   689  	if err != nil {
   690  		switch {
   691  		case k8serrors.IsAlreadyExists(err):
   692  			c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "Pod already exists, reused")
   693  			return gs, nil
   694  		case k8serrors.IsInvalid(err):
   695  			loggerForGameServer(gs, c.baseLogger).WithField("pod", pod).Errorf("Pod created is invalid")
   696  			gs, err = c.moveToErrorState(ctx, gs, err.Error())
   697  			return gs, err
   698  		case k8serrors.IsForbidden(err):
   699  			loggerForGameServer(gs, c.baseLogger).WithField("pod", pod).Errorf("Pod created is forbidden")
   700  			gs, err = c.moveToErrorState(ctx, gs, err.Error())
   701  			return gs, err
   702  		default:
   703  			c.recorder.Eventf(gs, corev1.EventTypeWarning, string(gs.Status.State), "error creating Pod for GameServer %s", gs.Name)
   704  			return gs, errors.Wrapf(err, "error creating Pod for GameServer %s", gs.Name)
   705  		}
   706  	}
   707  	c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State),
   708  		fmt.Sprintf("Pod %s created", pod.ObjectMeta.Name))
   709  
   710  	return gs, nil
   711  }
   712  
   713  // sidecar creates the sidecar container for a given GameServer
   714  func (c *Controller) sidecar(gs *agonesv1.GameServer) corev1.Container {
   715  	sidecar := corev1.Container{
   716  		Name:  sdkserverSidecarName,
   717  		Image: c.sidecarImage,
   718  		Env: []corev1.EnvVar{
   719  			{
   720  				Name:  "GAMESERVER_NAME",
   721  				Value: gs.ObjectMeta.Name,
   722  			},
   723  			{
   724  				Name: "POD_NAMESPACE",
   725  				ValueFrom: &corev1.EnvVarSource{
   726  					FieldRef: &corev1.ObjectFieldSelector{
   727  						FieldPath: "metadata.namespace",
   728  					},
   729  				},
   730  			},
   731  			{
   732  				Name:  "FEATURE_GATES",
   733  				Value: runtime.EncodeFeatures(),
   734  			},
   735  			{
   736  				Name:  "LOG_LEVEL",
   737  				Value: string(gs.Spec.SdkServer.LogLevel),
   738  			},
   739  			{
   740  				Name:  "REQUESTS_RATE_LIMIT",
   741  				Value: c.sidecarRequestsRateLimit.String(),
   742  			},
   743  		},
   744  		Resources: corev1.ResourceRequirements{},
   745  		LivenessProbe: &corev1.Probe{
   746  			ProbeHandler: corev1.ProbeHandler{
   747  				HTTPGet: &corev1.HTTPGetAction{
   748  					Path: "/healthz",
   749  					Port: intstr.FromInt(8080),
   750  				},
   751  			},
   752  			InitialDelaySeconds: 3,
   753  			PeriodSeconds:       3,
   754  		},
   755  	}
   756  
   757  	if gs.Spec.SdkServer.GRPCPort != 0 {
   758  		sidecar.Args = append(sidecar.Args, fmt.Sprintf("--grpc-port=%d", gs.Spec.SdkServer.GRPCPort))
   759  	}
   760  
   761  	if gs.Spec.SdkServer.HTTPPort != 0 {
   762  		sidecar.Args = append(sidecar.Args, fmt.Sprintf("--http-port=%d", gs.Spec.SdkServer.HTTPPort))
   763  	}
   764  
   765  	requests := corev1.ResourceList{}
   766  	if !c.sidecarCPURequest.IsZero() {
   767  		requests[corev1.ResourceCPU] = c.sidecarCPURequest
   768  	}
   769  	if !c.sidecarMemoryRequest.IsZero() {
   770  		requests[corev1.ResourceMemory] = c.sidecarMemoryRequest
   771  	}
   772  	sidecar.Resources.Requests = requests
   773  
   774  	limits := corev1.ResourceList{}
   775  	if !c.sidecarCPULimit.IsZero() {
   776  		limits[corev1.ResourceCPU] = c.sidecarCPULimit
   777  	}
   778  	if !c.sidecarMemoryLimit.IsZero() {
   779  		limits[corev1.ResourceMemory] = c.sidecarMemoryLimit
   780  	}
   781  	sidecar.Resources.Limits = limits
   782  
   783  	if c.alwaysPullSidecarImage {
   784  		sidecar.ImagePullPolicy = corev1.PullAlways
   785  	}
   786  
   787  	sidecar.SecurityContext = &corev1.SecurityContext{
   788  		AllowPrivilegeEscalation: ptr.To(false),
   789  		RunAsNonRoot:             ptr.To(true),
   790  		RunAsUser:                ptr.To(int64(c.sidecarRunAsUser)),
   791  	}
   792  
   793  	return sidecar
   794  }
   795  
   796  // addGameServerHealthCheck adds the http health check to the GameServer container
   797  func (c *Controller) addGameServerHealthCheck(gs *agonesv1.GameServer, pod *corev1.Pod) error {
   798  	if gs.Spec.Health.Disabled {
   799  		return nil
   800  	}
   801  
   802  	return gs.ApplyToPodContainer(pod, gs.Spec.Container, func(c corev1.Container) corev1.Container {
   803  		if c.LivenessProbe == nil {
   804  			c.LivenessProbe = &corev1.Probe{
   805  				ProbeHandler: corev1.ProbeHandler{
   806  					HTTPGet: &corev1.HTTPGetAction{
   807  						Path: "/gshealthz",
   808  						Port: intstr.FromInt(8080),
   809  					},
   810  				},
   811  				// The sidecar relies on kubelet to delay by InitialDelaySeconds after the
   812  				// container is started (after image pull, etc).
   813  				InitialDelaySeconds: gs.Spec.Health.InitialDelaySeconds,
   814  				PeriodSeconds:       gs.Spec.Health.PeriodSeconds,
   815  
   816  				// By the time /gshealthz returns unhealthy, the sidecar has already evaluated
   817  				// {FailureThreshold in a row} failed health checks, so in theory on the kubelet
   818  				// side, one failure is sufficient to know the game server is unhealthy. However,
   819  				// with only one failure, if the sidecar doesn't come up at all, we unnecessarily
   820  				// restart the game server. So use FailureThreshold as startup wiggle-room as well.
   821  				//
   822  				// Note that in general, FailureThreshold could also be infinite - the controller
   823  				// and sidecar are responsible for health management.
   824  				FailureThreshold: gs.Spec.Health.FailureThreshold,
   825  			}
   826  		}
   827  
   828  		return c
   829  	})
   830  }
   831  
   832  func (c *Controller) addSDKServerEnvVars(gs *agonesv1.GameServer, pod *corev1.Pod) {
   833  	sdkEnvVars := sdkEnvironmentVariables(gs)
   834  	if sdkEnvVars == nil {
   835  		// If a gameserver was created before 1.1 when we started defaulting the grpc and http ports,
   836  		// don't change any container spec.
   837  		return
   838  	}
   839  
   840  	for i := range pod.Spec.InitContainers {
   841  		c := &pod.Spec.InitContainers[i]
   842  		if c.Name != sdkserverSidecarName {
   843  			addEnvVarsToContainer(c, sdkEnvVars)
   844  			pod.Spec.InitContainers[i] = *c
   845  		}
   846  	}
   847  
   848  	for i := range pod.Spec.Containers {
   849  		c := &pod.Spec.Containers[i]
   850  		if c.Name != sdkserverSidecarName {
   851  			addEnvVarsToContainer(c, sdkEnvVars)
   852  			pod.Spec.Containers[i] = *c
   853  		}
   854  	}
   855  }
   856  
   857  func addEnvVarsToContainer(c *corev1.Container, sdkEnvVars []corev1.EnvVar) {
   858  	// Filter out environment variables that have reserved names.
   859  	// From https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
   860  	env := c.Env[:0]
   861  	for _, e := range c.Env {
   862  		if !reservedEnvironmentVariableName(e.Name) {
   863  			env = append(env, e)
   864  		}
   865  	}
   866  	env = append(env, sdkEnvVars...)
   867  	c.Env = env
   868  }
   869  
   870  func reservedEnvironmentVariableName(name string) bool {
   871  	return name == grpcPortEnvVar || name == httpPortEnvVar
   872  }
   873  
   874  func sdkEnvironmentVariables(gs *agonesv1.GameServer) []corev1.EnvVar {
   875  	var env []corev1.EnvVar
   876  	if gs.Spec.SdkServer.GRPCPort != 0 {
   877  		env = append(env, corev1.EnvVar{
   878  			Name:  grpcPortEnvVar,
   879  			Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort)),
   880  		})
   881  	}
   882  	if gs.Spec.SdkServer.HTTPPort != 0 {
   883  		env = append(env, corev1.EnvVar{
   884  			Name:  httpPortEnvVar,
   885  			Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort)),
   886  		})
   887  	}
   888  	return env
   889  }
   890  
   891  // syncGameServerStartingState looks for a pod that has been scheduled for this GameServer
   892  // and then sets the Status > Address and Ports values.
   893  func (c *Controller) syncGameServerStartingState(ctx context.Context, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   894  	if !(gs.Status.State == agonesv1.GameServerStateStarting && gs.ObjectMeta.DeletionTimestamp.IsZero()) {
   895  		return gs, nil
   896  	}
   897  	if _, isDev := gs.GetDevAddress(); isDev {
   898  		return gs, nil
   899  	}
   900  
   901  	loggerForGameServer(gs, c.baseLogger).Debug("Syncing Starting GameServerState")
   902  
   903  	// there should be a pod (although it may not have a scheduled container),
   904  	// so if there is an error of any kind, then move this to queue backoff
   905  	pod, err := c.gameServerPod(gs)
   906  	if err != nil {
   907  		// expected to happen, so don't log it.
   908  		if k8serrors.IsNotFound(err) {
   909  			return nil, workerqueue.NewTraceError(err)
   910  		}
   911  
   912  		// do log if it's something other than NotFound, since that's weird.
   913  		return nil, err
   914  	}
   915  	if pod.Spec.NodeName == "" {
   916  		return gs, workerqueue.NewTraceError(errors.Errorf("node not yet populated for Pod %s", pod.ObjectMeta.Name))
   917  	}
   918  
   919  	// Ensure the pod IPs are populated
   920  	if len(pod.Status.PodIPs) == 0 {
   921  		return gs, workerqueue.NewTraceError(errors.Errorf("pod IPs not yet populated for Pod %s", pod.ObjectMeta.Name))
   922  	}
   923  
   924  	node, err := c.nodeLister.Get(pod.Spec.NodeName)
   925  	if err != nil {
   926  		return gs, errors.Wrapf(err, "error retrieving node %s for Pod %s", pod.Spec.NodeName, pod.ObjectMeta.Name)
   927  	}
   928  	gsCopy := gs.DeepCopy()
   929  	gsCopy, err = applyGameServerAddressAndPort(gsCopy, node, pod, c.controllerHooks.SyncPodPortsToGameServer)
   930  	if err != nil {
   931  		return gs, err
   932  	}
   933  
   934  	gsCopy.Status.State = agonesv1.GameServerStateScheduled
   935  	gs, err = c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
   936  	if err != nil {
   937  		return gs, errors.Wrapf(err, "error updating GameServer %s to Scheduled state", gs.Name)
   938  	}
   939  	c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "Address and port populated")
   940  
   941  	return gs, nil
   942  }
   943  
   944  // syncGameServerRequestReadyState checks if the Game Server is Requesting to be ready,
   945  // and then adds the IP and Port information to the Status and marks the GameServer
   946  // as Ready
   947  func (c *Controller) syncGameServerRequestReadyState(ctx context.Context, gs *agonesv1.GameServer) (*agonesv1.GameServer, error) {
   948  	if !(gs.Status.State == agonesv1.GameServerStateRequestReady && gs.ObjectMeta.DeletionTimestamp.IsZero()) ||
   949  		gs.Status.State == agonesv1.GameServerStateUnhealthy {
   950  		return gs, nil
   951  	}
   952  	if _, isDev := gs.GetDevAddress(); isDev {
   953  		return gs, nil
   954  	}
   955  
   956  	loggerForGameServer(gs, c.baseLogger).Debug("Syncing RequestReady State")
   957  
   958  	gsCopy := gs.DeepCopy()
   959  
   960  	pod, err := c.gameServerPod(gs)
   961  	// NotFound should never happen, and if it does -- something bad happened,
   962  	// so go into workerqueue backoff.
   963  	if err != nil {
   964  		return nil, err
   965  	}
   966  
   967  	// if the address hasn't been populated, and the Ready request comes
   968  	// before the controller has had a chance to do it, then
   969  	// do it here instead
   970  	addressPopulated := false
   971  	if gs.Status.NodeName == "" {
   972  		addressPopulated = true
   973  		if pod.Spec.NodeName == "" {
   974  			return gs, workerqueue.NewTraceError(errors.Errorf("node not yet populated for Pod %s", pod.ObjectMeta.Name))
   975  		}
   976  		node, err := c.nodeLister.Get(pod.Spec.NodeName)
   977  		if err != nil {
   978  			return gs, errors.Wrapf(err, "error retrieving node %s for Pod %s", pod.Spec.NodeName, pod.ObjectMeta.Name)
   979  		}
   980  		gsCopy, err = applyGameServerAddressAndPort(gsCopy, node, pod, c.controllerHooks.SyncPodPortsToGameServer)
   981  		if err != nil {
   982  			return gs, err
   983  		}
   984  	}
   985  
   986  	gsCopy, err = c.applyGameServerReadyContainerIDAnnotation(ctx, gsCopy, pod)
   987  	if err != nil {
   988  		return gs, err
   989  	}
   990  
   991  	gsCopy.Status.State = agonesv1.GameServerStateReady
   992  	gs, err = c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
   993  	if err != nil {
   994  		return gs, errors.Wrapf(err, "error setting Ready, Port and address on GameServer %s Status", gs.ObjectMeta.Name)
   995  	}
   996  
   997  	if addressPopulated {
   998  		c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "Address and port populated")
   999  	}
  1000  	c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "SDK.Ready() complete")
  1001  	return gs, nil
  1002  }
  1003  
  1004  // applyGameServerReadyContainerIDAnnotation updates the GameServer and its corresponding Pod with an annotation
  1005  // indicating the ID of the container that is running the game server, once it's in a Running state.
  1006  func (c *Controller) applyGameServerReadyContainerIDAnnotation(ctx context.Context, gsCopy *agonesv1.GameServer, pod *corev1.Pod) (*agonesv1.GameServer, error) {
  1007  	// if there is a sidecar container, we escape right away. On move to stable, this method can be deleted.
  1008  	if runtime.FeatureEnabled(runtime.FeatureSidecarContainers) {
  1009  		return gsCopy, nil
  1010  	}
  1011  
  1012  	// track the ready gameserver container, so we can determine that after this point, we should move to Unhealthy
  1013  	// if there is a container crash/restart after we move to Ready
  1014  	for _, cs := range pod.Status.ContainerStatuses {
  1015  		if cs.Name == gsCopy.Spec.Container {
  1016  			if _, ok := gsCopy.ObjectMeta.Annotations[agonesv1.GameServerReadyContainerIDAnnotation]; !ok {
  1017  				// check to make sure this container is actually running. If there was a recent crash, the cache may
  1018  				// not yet have the newer, running container.
  1019  				if cs.State.Running == nil {
  1020  					return nil, workerqueue.NewTraceError(fmt.Errorf("game server container for GameServer %s in namespace %s is not currently running, try again", gsCopy.ObjectMeta.Name, gsCopy.ObjectMeta.Namespace))
  1021  				}
  1022  				gsCopy.ObjectMeta.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = cs.ContainerID
  1023  			}
  1024  			break
  1025  		}
  1026  	}
  1027  	// Verify that we found the game server container - we may have a stale cache where pod is missing ContainerStatuses.
  1028  	if _, ok := gsCopy.ObjectMeta.Annotations[agonesv1.GameServerReadyContainerIDAnnotation]; !ok {
  1029  		return nil, workerqueue.NewTraceError(fmt.Errorf("game server container for GameServer %s in namespace %s not present in pod status, try again", gsCopy.ObjectMeta.Name, gsCopy.ObjectMeta.Namespace))
  1030  	}
  1031  
  1032  	// Also update the pod with the same annotation, so we can check if the Pod data is up-to-date, now and also in the HealthController.
  1033  	// But if it is already set, then ignore it, since we only need to do this one time.
  1034  	if _, ok := pod.ObjectMeta.Annotations[agonesv1.GameServerReadyContainerIDAnnotation]; !ok {
  1035  		podCopy := pod.DeepCopy()
  1036  		if podCopy.ObjectMeta.Annotations == nil {
  1037  			podCopy.ObjectMeta.Annotations = map[string]string{}
  1038  		}
  1039  
  1040  		podCopy.ObjectMeta.Annotations[agonesv1.GameServerReadyContainerIDAnnotation] = gsCopy.ObjectMeta.Annotations[agonesv1.GameServerReadyContainerIDAnnotation]
  1041  		if _, err := c.podGetter.Pods(pod.ObjectMeta.Namespace).Update(ctx, podCopy, metav1.UpdateOptions{}); err != nil {
  1042  			return nil, errors.Wrapf(err, "error updating ready annotation on Pod: %s", pod.ObjectMeta.Name)
  1043  		}
  1044  	}
  1045  	return gsCopy, nil
  1046  }
  1047  
  1048  // syncGameServerShutdownState deletes the GameServer (and therefore the backing Pod) if it is in shutdown state
  1049  func (c *Controller) syncGameServerShutdownState(ctx context.Context, gs *agonesv1.GameServer) error {
  1050  	if !(gs.Status.State == agonesv1.GameServerStateShutdown && gs.ObjectMeta.DeletionTimestamp.IsZero()) {
  1051  		return nil
  1052  	}
  1053  
  1054  	loggerForGameServer(gs, c.baseLogger).Debug("Syncing Shutdown State")
  1055  	// be explicit about where to delete.
  1056  	p := metav1.DeletePropagationBackground
  1057  	err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Delete(ctx, gs.ObjectMeta.Name, metav1.DeleteOptions{PropagationPolicy: &p})
  1058  	if err != nil {
  1059  		return errors.Wrapf(err, "error deleting Game Server %s", gs.ObjectMeta.Name)
  1060  	}
  1061  	c.recorder.Event(gs, corev1.EventTypeNormal, string(gs.Status.State), "Deletion started")
  1062  	return nil
  1063  }
  1064  
  1065  // moveToErrorState moves the GameServer to the error state
  1066  func (c *Controller) moveToErrorState(ctx context.Context, gs *agonesv1.GameServer, msg string) (*agonesv1.GameServer, error) {
  1067  	gsCopy := gs.DeepCopy()
  1068  	if gsCopy.Annotations == nil {
  1069  		gsCopy.Annotations = make(map[string]string, 1)
  1070  	}
  1071  	gsCopy.Annotations[agonesv1.GameServerErroredAtAnnotation] = time.Now().Format(time.RFC3339)
  1072  	gsCopy.Status.State = agonesv1.GameServerStateError
  1073  
  1074  	gs, err := c.gameServerGetter.GameServers(gs.ObjectMeta.Namespace).Update(ctx, gsCopy, metav1.UpdateOptions{})
  1075  	if err != nil {
  1076  		return gs, errors.Wrapf(err, "error moving GameServer %s to Error State", gs.ObjectMeta.Name)
  1077  	}
  1078  
  1079  	c.recorder.Event(gs, corev1.EventTypeWarning, string(gs.Status.State), msg)
  1080  	return gs, nil
  1081  }
  1082  
  1083  // gameServerPod returns the Pod for this Game Server, or an error if there are none,
  1084  // or it cannot be determined (there are more than one, which should not happen)
  1085  func (c *Controller) gameServerPod(gs *agonesv1.GameServer) (*corev1.Pod, error) {
  1086  	// If the game server is a dev server we do not create a pod for it, return an empty pod.
  1087  	if _, isDev := gs.GetDevAddress(); isDev {
  1088  		return &corev1.Pod{}, nil
  1089  	}
  1090  
  1091  	pod, err := c.podLister.Pods(gs.ObjectMeta.Namespace).Get(gs.ObjectMeta.Name)
  1092  
  1093  	// if not found, propagate this error up, so we can use it in checks
  1094  	if k8serrors.IsNotFound(err) {
  1095  		return nil, err
  1096  	}
  1097  
  1098  	if !metav1.IsControlledBy(pod, gs) {
  1099  		return nil, k8serrors.NewNotFound(corev1.Resource("pod"), gs.ObjectMeta.Name)
  1100  	}
  1101  
  1102  	return pod, errors.Wrapf(err, "error retrieving pod for GameServer %s", gs.ObjectMeta.Name)
  1103  }