diff --git a/install/helm/agones/templates/crds/_gameserverspecschema.yaml b/install/helm/agones/templates/crds/_gameserverspecschema.yaml index 09c1131e9f..03cd5db3fe 100644 --- a/install/helm/agones/templates/crds/_gameserverspecschema.yaml +++ b/install/helm/agones/templates/crds/_gameserverspecschema.yaml @@ -154,6 +154,21 @@ properties: title: The initial player capacity of this Game Server minimum: 0 {{- if .featureSafeToEvict }} + eviction: + type: object + title: Eviction tolerance of the game server + properties: + safe: + type: string + title: Game server supports termination via SIGTERM + description: | + - Never: The game server should run to completion. Agones sets Pod annotation `cluster-autoscaler.kubernetes.io/safe-to-evict: "false"` and label `agones.dev/safe-to-evict: "false"`, which matches a restrictive PodDisruptionBudget. + - OnUpgrade: On SIGTERM, the game server will exit within `terminationGracePeriodSeconds` or be terminated; Agones sets Pod annotation `cluster-autoscaler.kubernetes.io/safe-to-evict: "false"`, which blocks evictions by Cluster Autoscaler. Evictions from node upgrades proceed normally. + - Always: On SIGTERM, the game server will exit within `terminationGracePeriodSeconds` or be terminated, typically within 10m; Agones sets Pod annotation `cluster-autoscaler.kubernetes.io/safe-to-evict: "true"`, which allows evictions by Cluster Autoscaler. + enum: + - Always + - OnUpgrade + - Never immutableReplicas: type: integer title: Immutable count of Pods to a GameServer. Always 1. (Implementation detail of implementing the Scale subresource.) diff --git a/install/helm/agones/templates/crds/_gameserverstatus.yaml b/install/helm/agones/templates/crds/_gameserverstatus.yaml index 3d77aced52..f15f0db753 100644 --- a/install/helm/agones/templates/crds/_gameserverstatus.yaml +++ b/install/helm/agones/templates/crds/_gameserverstatus.yaml @@ -66,6 +66,15 @@ status: items: type: string {{- if .featureSafeToEvict }} + eviction: + type: object + properties: + safe: + type: string + enum: + - Always + - OnUpgrade + - Never immutableReplicas: type: integer title: Immutable count of Pods to a GameServer. Always 1. (Implementation detail of implementing the Scale subresource.) diff --git a/pkg/apis/agones/v1/apihooks.go b/pkg/apis/agones/v1/apihooks.go index 04bad0e75b..5bb65d9203 100644 --- a/pkg/apis/agones/v1/apihooks.go +++ b/pkg/apis/agones/v1/apihooks.go @@ -15,6 +15,8 @@ package v1 import ( + "agones.dev/agones/pkg/util/runtime" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -28,6 +30,9 @@ type APIHooks interface { // MutateGameServerPodSpec is called by createGameServerPod to allow for product specific pod mutation. MutateGameServerPodSpec(*GameServerSpec, *corev1.PodSpec) error + + // SetEviction is called by gs.Pod to enforce GameServer.Status.Eviction. + SetEviction(EvictionSafe, *corev1.Pod) error } var apiHooks APIHooks = generic{} @@ -45,3 +50,37 @@ type generic struct{} func (generic) ValidateGameServerSpec(*GameServerSpec) []metav1.StatusCause { return nil } func (generic) MutateGameServerPodSpec(*GameServerSpec, *corev1.PodSpec) error { return nil } + +// SetEviction sets disruptions controls based on GameServer.Status.Eviction. +func (generic) SetEviction(safe EvictionSafe, pod *corev1.Pod) error { + if !runtime.FeatureEnabled(runtime.FeatureSafeToEvict) { + return nil + } + if _, exists := pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation]; !exists { + switch safe { + case EvictionSafeAlways: + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = True + case EvictionSafeOnUpgrade, EvictionSafeNever: + // For EvictionSafeOnUpgrade and EvictionSafeNever, we block Cluster Autoscaler. + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = False + default: + return errors.Errorf("unknown eviction.safe value %q", string(safe)) + } + } + if _, exists := pod.ObjectMeta.Labels[SafeToEvictLabel]; !exists { + switch safe { + case EvictionSafeAlways, EvictionSafeOnUpgrade: + // For EvictionSafeAlways and EvictionSafeOnUpgrade, we use a label value + // that does not match the agones-gameserver-safe-to-evict-false PDB. But + // we go ahead and label it, in case someone wants to adopt custom logic + // for this group of game servers. + pod.ObjectMeta.Labels[SafeToEvictLabel] = True + case EvictionSafeNever: + // For EvictionSafeNever, match gones-gameserver-safe-to-evict-false PDB. + pod.ObjectMeta.Labels[SafeToEvictLabel] = False + default: + return errors.Errorf("unknown eviction.safe value %q", string(safe)) + } + } + return nil +} diff --git a/pkg/apis/agones/v1/apihooks_test.go b/pkg/apis/agones/v1/apihooks_test.go new file mode 100644 index 0000000000..ef89b1730b --- /dev/null +++ b/pkg/apis/agones/v1/apihooks_test.go @@ -0,0 +1,123 @@ +// Copyright 2022 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. + +package v1 + +import ( + "testing" + + "agones.dev/agones/pkg/util/runtime" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestSetEviction(t *testing.T) { + runtime.FeatureTestMutex.Lock() + defer runtime.FeatureTestMutex.Unlock() + + emptyPodAnd := func(f func(*corev1.Pod)) *corev1.Pod { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + Labels: map[string]string{}, + }, + } + f(pod) + return pod + } + for desc, tc := range map[string]struct { + featureFlags string + safeToEvict EvictionSafe + pod *corev1.Pod + wantPod *corev1.Pod + }{ + "SafeToEvict feature gate disabled => no change": { + featureFlags: "SafeToEvict=false", + // intentionally leave pod nil, it'll crash if anything's touched. + }, + "SafeToEvict: Always, no incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: EvictionSafeAlways, + pod: emptyPodAnd(func(*corev1.Pod) {}), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = True + pod.ObjectMeta.Labels[SafeToEvictLabel] = True + }), + }, + "SafeToEvict: OnUpgrade, no incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: EvictionSafeOnUpgrade, + pod: emptyPodAnd(func(*corev1.Pod) {}), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = False + pod.ObjectMeta.Labels[SafeToEvictLabel] = True + }), + }, + "SafeToEvict: Never, no incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: EvictionSafeNever, + pod: emptyPodAnd(func(*corev1.Pod) {}), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = False + pod.ObjectMeta.Labels[SafeToEvictLabel] = False + }), + }, + "SafeToEvict: Always, incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: EvictionSafeAlways, + pod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = "just don't touch, ok?" + pod.ObjectMeta.Labels[SafeToEvictLabel] = "seriously, leave it" + }), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = "just don't touch, ok?" + pod.ObjectMeta.Labels[SafeToEvictLabel] = "seriously, leave it" + }), + }, + "SafeToEvict: OnUpgrade, incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: EvictionSafeOnUpgrade, + pod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = "better not touch" + pod.ObjectMeta.Labels[SafeToEvictLabel] = "not another one" + }), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = "better not touch" + pod.ObjectMeta.Labels[SafeToEvictLabel] = "not another one" + }), + }, + "SafeToEvict: Never, incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: EvictionSafeNever, + pod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = "a passthrough" + pod.ObjectMeta.Labels[SafeToEvictLabel] = "or is it passthru?" + }), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = "a passthrough" + pod.ObjectMeta.Labels[SafeToEvictLabel] = "or is it passthru?" + }), + }, + } { + t.Run(desc, func(t *testing.T) { + err := runtime.ParseFeatures(tc.featureFlags) + assert.NoError(t, err) + + err = (generic{}).SetEviction(tc.safeToEvict, tc.pod) + assert.NoError(t, err) + assert.Equal(t, tc.wantPod, tc.pod) + }) + } +} diff --git a/pkg/apis/agones/v1/gameserver.go b/pkg/apis/agones/v1/gameserver.go index 80d1d1ca74..3d27ad2670 100644 --- a/pkg/apis/agones/v1/gameserver.go +++ b/pkg/apis/agones/v1/gameserver.go @@ -72,6 +72,16 @@ const ( // This will mean that users will need to lookup what port has been opened through the server side SDK. Passthrough PortPolicy = "Passthrough" + // EvictionSafeAlways means the game server supports termination via SIGTERM, and wants eviction signals + // from Cluster Autoscaler scaledown and node upgrades. + EvictionSafeAlways EvictionSafe = "Always" + // EvictionSafeOnUpgrade means the game server supports termination via SIGTERM, and wants eviction signals + // from node upgrades, but not Cluster Autoscaler scaledown. + EvictionSafeOnUpgrade EvictionSafe = "OnUpgrade" + // EvictionSafeNever means the game server should run to completion and may not understand SIGTERM. Eviction + // from ClusterAutoscaler and upgrades should both be blocked. + EvictionSafeNever EvictionSafe = "Never" + // ProtocolTCPUDP Protocol exposes the hostPort allocated for this container for both TCP and UDP. ProtocolTCPUDP corev1.Protocol = "TCPUDP" @@ -104,6 +114,13 @@ const ( // determine if a pod can safely be evicted to compact a cluster by moving pods between nodes // and scaling down nodes. PodSafeToEvictAnnotation = "cluster-autoscaler.kubernetes.io/safe-to-evict" + // SafeToEvictLabel is a label that, when "false", matches the restrictive PDB agones-gameserver-safe-to-evict-false. + SafeToEvictLabel = agones.GroupName + "/safe-to-evict" + + // True is the string "true" to appease the goconst lint. + True = "true" + // False is the string "false" to appease the goconst lint. + False = "false" ) var ( @@ -162,6 +179,9 @@ type GameServerSpec struct { // (Alpha, PlayerTracking feature flag) Players provides the configuration for player tracking features. // +optional Players *PlayersSpec `json:"players,omitempty"` + // (Alpha, SafeToEvict feature flag) Eviction specifies the eviction tolerance of the GameServer. Defaults to "Never". + // +optional + Eviction Eviction `json:"eviction,omitempty"` // immutableReplicas is present in gameservers.agones.dev but omitted here (it's always 1). } @@ -170,12 +190,25 @@ type PlayersSpec struct { InitialCapacity int64 `json:"initialCapacity,omitempty"` } +// Eviction specifies the eviction tolerance of the GameServer +type Eviction struct { + // (Alpha, SafeToEvict feature flag) + // Game server supports termination via SIGTERM: + // - Always: Allow eviction for both Cluster Autoscaler and node drain for upgrades + // - OnUpgrade: Allow eviction for upgrades alone + // - Never (default): Pod should run to completion + Safe EvictionSafe `json:"safe,omitempty"` +} + // GameServerState is the state for the GameServer type GameServerState string // PortPolicy is the port policy for the GameServer type PortPolicy string +// EvictionSafe specified whether the game server supports termination via SIGTERM +type EvictionSafe string + // Health configures health checking on the GameServer type Health struct { // Disabled is whether health checking is disabled or not @@ -235,6 +268,8 @@ type GameServerStatus struct { // [FeatureFlag:PlayerTracking] // +optional Players *PlayerStatus `json:"players"` + // (Alpha, SafeToEvict feature flag) Eviction specifies the eviction tolerance of the GameServer. + Eviction Eviction `json:"eviction,omitempty"` // immutableReplicas is present in gameservers.agones.dev but omitted here (it's always 1). } @@ -271,6 +306,7 @@ func (gss *GameServerSpec) ApplyDefaults() { gss.applyContainerDefaults() gss.applyPortDefaults() gss.applyHealthDefaults() + gss.applyEvictionDefaults() gss.applySchedulingDefaults() gss.applySdkServerDefaults() } @@ -330,6 +366,8 @@ func (gs *GameServer) applyStatusDefaults() { gs.Status.Players.Capacity = gs.Spec.Players.InitialCapacity } } + + gs.applyEvictionStatus() } // applyPortDefaults applies default values for all ports @@ -356,6 +394,25 @@ func (gss *GameServerSpec) applySchedulingDefaults() { } } +func (gss *GameServerSpec) applyEvictionDefaults() { + if !runtime.FeatureEnabled(runtime.FeatureSafeToEvict) { + return + } + if gss.Eviction.Safe == "" { + gss.Eviction.Safe = EvictionSafeNever + } +} + +func (gs *GameServer) applyEvictionStatus() { + if !runtime.FeatureEnabled(runtime.FeatureSafeToEvict) { + return + } + gs.Status.Eviction = gs.Spec.Eviction + if gs.Spec.Template.ObjectMeta.Annotations[PodSafeToEvictAnnotation] == "true" { + gs.Status.Eviction.Safe = EvictionSafeAlways + } +} + // Validate validates the GameServerSpec configuration. // devAddress is a specific IP address used for local Gameservers, for fleets "" is used // If a GameServer Spec is invalid there will be > 0 values in @@ -373,6 +430,16 @@ func (gss *GameServerSpec) Validate(devAddress string) ([]metav1.StatusCause, bo } } + if !runtime.FeatureEnabled(runtime.FeatureSafeToEvict) { + if gss.Eviction.Safe != "" { + causes = append(causes, metav1.StatusCause{ + Type: metav1.CauseTypeFieldValueNotSupported, + Field: "eviction.safe", + Message: fmt.Sprintf("Value cannot be set unless feature flag %s is enabled", runtime.FeatureSafeToEvict), + }) + } + } + if devAddress != "" { // verify that the value is a valid IP address. if net.ParseIP(devAddress) == nil { @@ -631,6 +698,9 @@ func (gs *GameServer) Pod(sidecars ...corev1.Container) (*corev1.Pod, error) { if err := apiHooks.MutateGameServerPodSpec(&gs.Spec, &pod.Spec); err != nil { return nil, err } + if err := apiHooks.SetEviction(gs.Status.Eviction.Safe, pod); err != nil { + return nil, err + } return pod, nil } @@ -660,13 +730,16 @@ func (gs *GameServer) podObjectMeta(pod *corev1.Pod) { ref := metav1.NewControllerRef(gs, SchemeGroupVersion.WithKind("GameServer")) pod.ObjectMeta.OwnerReferences = append(pod.ObjectMeta.OwnerReferences, *ref) - // This means that the autoscaler cannot remove the Node that this Pod is on. - // (and evict the Pod in the process). Only set the value if it has not already - // been configured in the pod template (to not override user specified behavior). - // We only set this for packed game servers, under the assumption that if - // game servers are distributed then the cluster autoscaler isn't likely running. - if _, exists := pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation]; !exists && gs.Spec.Scheduling == apis.Packed { - pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = "false" + // When SafeToEvict=true, apiHooks.SetEviction manages disruption controls. + if !runtime.FeatureEnabled(runtime.FeatureSafeToEvict) { + // This means that the autoscaler cannot remove the Node that this Pod is on. + // (and evict the Pod in the process). Only set the value if it has not already + // been configured in the pod template (to not override user specified behavior). + // We only set this for packed game servers, under the assumption that if + // game servers are distributed then the cluster autoscaler isn't likely running. + if _, exists := pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation]; !exists && gs.Spec.Scheduling == apis.Packed { + pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation] = "false" + } } // Add Agones version into Pod Annotations diff --git a/pkg/apis/agones/v1/gameserver_test.go b/pkg/apis/agones/v1/gameserver_test.go index 01cd27973c..b6f7534f08 100644 --- a/pkg/apis/agones/v1/gameserver_test.go +++ b/pkg/apis/agones/v1/gameserver_test.go @@ -134,7 +134,20 @@ func TestGameServerApplyDefaults(t *testing.T) { ten := int64(10) + defaultGameServerAnd := func(f func(gss *GameServerSpec)) GameServer { + gs := GameServer{ + Spec: GameServerSpec{ + Ports: []GameServerPort{{ContainerPort: 999}}, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{Containers: []corev1.Container{ + {Name: "testing", Image: "testing/image"}, + }}}}, + } + f(&gs.Spec) + return gs + } type expected struct { + container string protocol corev1.Protocol state GameServerState policy PortPolicy @@ -142,6 +155,30 @@ func TestGameServerApplyDefaults(t *testing.T) { scheduling apis.SchedulingStrategy sdkServer SdkServer alphaPlayerCapacity *int64 + evictionSafeSpec EvictionSafe + evictionSafeStatus EvictionSafe + } + wantDefaultAnd := func(f func(e *expected)) expected { + e := expected{ + container: "testing", + protocol: "UDP", + state: GameServerStatePortAllocation, + policy: Dynamic, + scheduling: apis.Packed, + health: Health{ + Disabled: false, + FailureThreshold: 3, + InitialDelaySeconds: 5, + PeriodSeconds: 5, + }, + sdkServer: SdkServer{ + LogLevel: SdkServerLogLevelInfo, + GRPCPort: 9357, + HTTPPort: 9358, + }, + } + f(&e) + return e } data := map[string]struct { gameServer GameServer @@ -150,63 +187,25 @@ func TestGameServerApplyDefaults(t *testing.T) { expected expected }{ "set basic defaults on a very simple gameserver": { + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) {}), + expected: wantDefaultAnd(func(e *expected) {}), + }, + "PlayerTracking=true": { featureFlags: string(runtime.FeaturePlayerTracking) + "=true", - gameServer: GameServer{ - Spec: GameServerSpec{ - Players: &PlayersSpec{InitialCapacity: 10}, - Ports: []GameServerPort{{ContainerPort: 999}}, - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{Containers: []corev1.Container{ - {Name: "testing", Image: "testing/image"}, - }}}}, - }, - container: "testing", - expected: expected{ - protocol: "UDP", - state: GameServerStatePortAllocation, - policy: Dynamic, - scheduling: apis.Packed, - health: Health{ - Disabled: false, - FailureThreshold: 3, - InitialDelaySeconds: 5, - PeriodSeconds: 5, - }, - sdkServer: SdkServer{ - LogLevel: SdkServerLogLevelInfo, - GRPCPort: 9357, - HTTPPort: 9358, - }, - alphaPlayerCapacity: &ten, - }, + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Players = &PlayersSpec{InitialCapacity: 10} + }), + expected: wantDefaultAnd(func(e *expected) { + e.alphaPlayerCapacity = &ten + }), }, "defaults on passthrough": { - gameServer: GameServer{ - Spec: GameServerSpec{ - Ports: []GameServerPort{{PortPolicy: Passthrough}}, - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{Containers: []corev1.Container{ - {Name: "testing", Image: "testing/image"}, - }}}}, - }, - container: "testing", - expected: expected{ - protocol: "UDP", - state: GameServerStatePortAllocation, - policy: Passthrough, - scheduling: apis.Packed, - health: Health{ - Disabled: false, - FailureThreshold: 3, - InitialDelaySeconds: 5, - PeriodSeconds: 5, - }, - sdkServer: SdkServer{ - LogLevel: SdkServerLogLevelInfo, - GRPCPort: 9357, - HTTPPort: 9358, - }, - }, + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Ports[0].PortPolicy = Passthrough + }), + expected: wantDefaultAnd(func(e *expected) { + e.policy = Passthrough + }), }, "defaults are already set": { gameServer: GameServer{ @@ -235,165 +234,146 @@ func TestGameServerApplyDefaults(t *testing.T) { }, }, Status: GameServerStatus{State: "TestState"}}, - container: "testing2", - expected: expected{ - protocol: "TCP", - state: "TestState", - policy: Static, - scheduling: apis.Packed, - health: Health{ + expected: wantDefaultAnd(func(e *expected) { + e.container = "testing2" + e.protocol = "TCP" + e.state = "TestState" + e.health = Health{ Disabled: false, FailureThreshold: 10, InitialDelaySeconds: 11, PeriodSeconds: 12, - }, - sdkServer: SdkServer{ - LogLevel: SdkServerLogLevelInfo, - GRPCPort: 9357, - HTTPPort: 9358, - }, - }, + } + }), }, "set basic defaults on static gameserver": { - gameServer: GameServer{ - Spec: GameServerSpec{ - Ports: []GameServerPort{{PortPolicy: Static}}, - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "testing", Image: "testing/image"}}}}}, - }, - container: "testing", - expected: expected{ - protocol: "UDP", - state: GameServerStateCreating, - policy: Static, - scheduling: apis.Packed, - health: Health{ - Disabled: false, - FailureThreshold: 3, - InitialDelaySeconds: 5, - PeriodSeconds: 5, - }, - sdkServer: SdkServer{ - LogLevel: SdkServerLogLevelInfo, - GRPCPort: 9357, - HTTPPort: 9358, - }, - }, + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Ports[0].PortPolicy = Static + }), + expected: wantDefaultAnd(func(e *expected) { + e.state = GameServerStateCreating + e.policy = Static + }), }, "health is disabled": { - gameServer: GameServer{ - Spec: GameServerSpec{ - Ports: []GameServerPort{{ContainerPort: 999}}, - Health: Health{Disabled: true}, - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "testing", Image: "testing/image"}}}}}, - }, - container: "testing", - expected: expected{ - protocol: "UDP", - state: GameServerStatePortAllocation, - policy: Dynamic, - scheduling: apis.Packed, - health: Health{ - Disabled: true, - }, - sdkServer: SdkServer{ - LogLevel: SdkServerLogLevelInfo, - GRPCPort: 9357, - HTTPPort: 9358, - }, - }, + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Health = Health{Disabled: true} + }), + expected: wantDefaultAnd(func(e *expected) { + e.health = Health{Disabled: true} + }), }, "convert from legacy single port to multiple": { - gameServer: GameServer{ - Spec: GameServerSpec{ - Ports: []GameServerPort{ - { - ContainerPort: 777, - HostPort: 777, - PortPolicy: Static, - Protocol: corev1.ProtocolTCP, - }, - }, - Health: Health{Disabled: true}, - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "testing", Image: "testing/image"}}}}}, - }, - container: "testing", - expected: expected{ - protocol: corev1.ProtocolTCP, - state: GameServerStateCreating, - policy: Static, - scheduling: apis.Packed, - health: Health{Disabled: true}, - sdkServer: SdkServer{ - LogLevel: SdkServerLogLevelInfo, - GRPCPort: 9357, - HTTPPort: 9358, - }, - }, + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Ports[0] = GameServerPort{ + ContainerPort: 777, + HostPort: 777, + PortPolicy: Static, + Protocol: corev1.ProtocolTCP, + } + }), + expected: wantDefaultAnd(func(e *expected) { + e.protocol = "TCP" + e.state = GameServerStateCreating + }), }, "set Debug logging level": { - gameServer: GameServer{ - Spec: GameServerSpec{ - Ports: []GameServerPort{{ContainerPort: 999}}, - SdkServer: SdkServer{LogLevel: SdkServerLogLevelDebug}, - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{Containers: []corev1.Container{ - {Name: "testing", Image: "testing/image"}, - }}}}, - }, - container: "testing", - expected: expected{ - protocol: "UDP", - state: GameServerStatePortAllocation, - policy: Dynamic, - scheduling: apis.Packed, - health: Health{ - Disabled: false, - FailureThreshold: 3, - InitialDelaySeconds: 5, - PeriodSeconds: 5, - }, - sdkServer: SdkServer{ - LogLevel: SdkServerLogLevelDebug, - GRPCPort: 9357, - HTTPPort: 9358, - }, - }, + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.SdkServer = SdkServer{LogLevel: SdkServerLogLevelDebug} + }), + expected: wantDefaultAnd(func(e *expected) { + e.sdkServer.LogLevel = SdkServerLogLevelDebug + }), }, "set gRPC and HTTP ports on SDK Server": { - gameServer: GameServer{ - Spec: GameServerSpec{ - Ports: []GameServerPort{{ContainerPort: 999}}, - SdkServer: SdkServer{ - LogLevel: SdkServerLogLevelError, - GRPCPort: 19357, - HTTPPort: 19358, - }, - Template: corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{Containers: []corev1.Container{ - {Name: "testing", Image: "testing/image"}, - }}}}, - }, - container: "testing", - expected: expected{ - protocol: "UDP", - state: GameServerStatePortAllocation, - policy: Dynamic, - scheduling: apis.Packed, - health: Health{ - Disabled: false, - FailureThreshold: 3, - InitialDelaySeconds: 5, - PeriodSeconds: 5, - }, - sdkServer: SdkServer{ + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.SdkServer = SdkServer{ LogLevel: SdkServerLogLevelError, GRPCPort: 19357, HTTPPort: 19358, - }, - }, + } + }), + expected: wantDefaultAnd(func(e *expected) { + e.sdkServer = SdkServer{ + LogLevel: SdkServerLogLevelError, + GRPCPort: 19357, + HTTPPort: 19358, + } + }), + }, + "SafeToEvict gate off => no SafeToEvict fields": { + featureFlags: string(runtime.FeatureSafeToEvict) + "=false", + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) {}), + expected: wantDefaultAnd(func(e *expected) {}), + }, + "SafeToEvict gate on => SafeToEvict: Never": { + featureFlags: string(runtime.FeatureSafeToEvict) + "=true", + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) {}), + expected: wantDefaultAnd(func(e *expected) { + e.evictionSafeSpec = EvictionSafeNever + e.evictionSafeStatus = EvictionSafeNever + }), + }, + "SafeToEvict: Always": { + featureFlags: string(runtime.FeatureSafeToEvict) + "=true", + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Eviction.Safe = EvictionSafeAlways + }), + expected: wantDefaultAnd(func(e *expected) { + e.evictionSafeSpec = EvictionSafeAlways + e.evictionSafeStatus = EvictionSafeAlways + }), + }, + "SafeToEvict: OnUpgrade": { + featureFlags: string(runtime.FeatureSafeToEvict) + "=true", + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Eviction.Safe = EvictionSafeOnUpgrade + }), + expected: wantDefaultAnd(func(e *expected) { + e.evictionSafeSpec = EvictionSafeOnUpgrade + e.evictionSafeStatus = EvictionSafeOnUpgrade + }), + }, + "SafeToEvict: Never": { + featureFlags: string(runtime.FeatureSafeToEvict) + "=true", + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Eviction.Safe = EvictionSafeNever + }), + expected: wantDefaultAnd(func(e *expected) { + e.evictionSafeSpec = EvictionSafeNever + e.evictionSafeStatus = EvictionSafeNever + }), + }, + "SafeToEvict: Always inferred from safe-to-evict=true": { + featureFlags: string(runtime.FeatureSafeToEvict) + "=true", + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Template.ObjectMeta.Annotations = map[string]string{PodSafeToEvictAnnotation: "true"} + }), + expected: wantDefaultAnd(func(e *expected) { + e.evictionSafeSpec = EvictionSafeNever + e.evictionSafeStatus = EvictionSafeAlways + }), + }, + "Nothing inferred from safe-to-evict=false": { + featureFlags: string(runtime.FeatureSafeToEvict) + "=true", + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Template.ObjectMeta.Annotations = map[string]string{PodSafeToEvictAnnotation: "false"} + }), + expected: wantDefaultAnd(func(e *expected) { + e.evictionSafeSpec = EvictionSafeNever + e.evictionSafeStatus = EvictionSafeNever + }), + }, + "safe-to-evict=false AND SafeToEvict: Always => SafeToEvict: Always": { + featureFlags: string(runtime.FeatureSafeToEvict) + "=true", + gameServer: defaultGameServerAnd(func(gss *GameServerSpec) { + gss.Eviction.Safe = EvictionSafeAlways + gss.Template.ObjectMeta.Annotations = map[string]string{PodSafeToEvictAnnotation: "false"} + }), + expected: wantDefaultAnd(func(e *expected) { + e.evictionSafeSpec = EvictionSafeAlways + e.evictionSafeStatus = EvictionSafeAlways + }), }, } @@ -411,7 +391,7 @@ func TestGameServerApplyDefaults(t *testing.T) { spec := test.gameServer.Spec assert.Contains(t, test.gameServer.ObjectMeta.Finalizers, agones.GroupName) - assert.Equal(t, test.container, spec.Container) + assert.Equal(t, test.expected.container, spec.Container) assert.Equal(t, test.expected.protocol, spec.Ports[0].Protocol) assert.Equal(t, test.expected.state, test.gameServer.Status.State) assert.Equal(t, test.expected.scheduling, test.gameServer.Spec.Scheduling) @@ -423,6 +403,8 @@ func TestGameServerApplyDefaults(t *testing.T) { assert.Nil(t, test.gameServer.Spec.Players) assert.Nil(t, test.gameServer.Status.Players) } + assert.Equal(t, test.expected.evictionSafeSpec, spec.Eviction.Safe) + assert.Equal(t, test.expected.evictionSafeStatus, test.gameServer.Status.Eviction.Safe) }) } } @@ -1265,67 +1247,116 @@ func TestGameServerPodWithMultiplePortAllocations(t *testing.T) { } func TestGameServerPodObjectMeta(t *testing.T) { + runtime.FeatureTestMutex.Lock() + defer runtime.FeatureTestMutex.Unlock() + fixture := &GameServer{ObjectMeta: metav1.ObjectMeta{Name: "lucy"}, Spec: GameServerSpec{Container: "goat"}} - f := func(t *testing.T, gs *GameServer, pod *corev1.Pod) { - assert.Equal(t, gs.ObjectMeta.Name, pod.ObjectMeta.Name) - assert.Equal(t, gs.ObjectMeta.Namespace, pod.ObjectMeta.Namespace) - assert.Equal(t, GameServerLabelRole, pod.ObjectMeta.Labels[RoleLabel]) - assert.Equal(t, "gameserver", pod.ObjectMeta.Labels[agones.GroupName+"/role"]) - assert.Equal(t, gs.ObjectMeta.Name, pod.ObjectMeta.Labels[GameServerPodLabel]) - assert.Equal(t, "goat", pod.ObjectMeta.Annotations[GameServerContainerAnnotation]) - assert.True(t, metav1.IsControlledBy(pod, gs)) - } - - t.Run("packed", func(t *testing.T) { - gs := fixture.DeepCopy() - gs.Spec.Scheduling = apis.Packed - pod := &corev1.Pod{} - - gs.podObjectMeta(pod) - f(t, gs, pod) - - assert.Equal(t, "false", pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation]) - }) + for desc, tc := range map[string]struct { + featureFlags string + scheduling apis.SchedulingStrategy + wantSafe string + }{ + "packed, SafeToEvict=false": { + featureFlags: "SafeToEvict=false", + scheduling: apis.Packed, + wantSafe: "false", + }, + "distributed, SafeToEvict=false": { + featureFlags: "SafeToEvict=false", + scheduling: apis.Distributed, + }, + "packed, SafeToEvict=true": { + featureFlags: "SafeToEvict=true", + scheduling: apis.Packed, + }, + "distributed, SafeToEvict=true": { + featureFlags: "SafeToEvict=true", + scheduling: apis.Distributed, + }, + } { + t.Run(desc, func(t *testing.T) { + err := runtime.ParseFeatures(tc.featureFlags) + assert.NoError(t, err) - t.Run("distributed", func(t *testing.T) { - gs := fixture.DeepCopy() - gs.Spec.Scheduling = apis.Distributed - pod := &corev1.Pod{} + gs := fixture.DeepCopy() + gs.Spec.Scheduling = tc.scheduling + pod := &corev1.Pod{} - gs.podObjectMeta(pod) - f(t, gs, pod) + gs.podObjectMeta(pod) - assert.Equal(t, "", pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation]) - }) + assert.Equal(t, gs.ObjectMeta.Name, pod.ObjectMeta.Name) + assert.Equal(t, gs.ObjectMeta.Namespace, pod.ObjectMeta.Namespace) + assert.Equal(t, GameServerLabelRole, pod.ObjectMeta.Labels[RoleLabel]) + assert.Equal(t, "gameserver", pod.ObjectMeta.Labels[agones.GroupName+"/role"]) + assert.Equal(t, gs.ObjectMeta.Name, pod.ObjectMeta.Labels[GameServerPodLabel]) + assert.Equal(t, "goat", pod.ObjectMeta.Annotations[GameServerContainerAnnotation]) + assert.True(t, metav1.IsControlledBy(pod, gs)) + assert.Equal(t, tc.wantSafe, pod.ObjectMeta.Annotations[PodSafeToEvictAnnotation]) + }) + } } func TestGameServerPodAutoscalerAnnotations(t *testing.T) { + runtime.FeatureTestMutex.Lock() + defer runtime.FeatureTestMutex.Unlock() + testCases := []struct { + featureFlags string description string scheduling apis.SchedulingStrategy setAnnotation bool expectedAnnotation string }{ { - description: "Packed", + featureFlags: "SafeToEvict=false", + description: "Packed, SafeToEvict=false", scheduling: apis.Packed, expectedAnnotation: "false", }, { - description: "Distributed", + featureFlags: "SafeToEvict=false", + description: "Distributed, SafeToEvict=false", scheduling: apis.Distributed, expectedAnnotation: "", }, { - description: "Packed with autoscaler annotation", + featureFlags: "SafeToEvict=false", + description: "Packed with autoscaler annotation, SafeToEvict=false", scheduling: apis.Packed, setAnnotation: true, expectedAnnotation: "true", }, { - description: "Distributed with autoscaler annotation", + featureFlags: "SafeToEvict=false", + description: "Distributed with autoscaler annotation, SafeToEvict=false", + scheduling: apis.Distributed, + setAnnotation: true, + expectedAnnotation: "true", + }, + { + featureFlags: "SafeToEvict=true", + description: "Packed, SafeToEvict=true", + scheduling: apis.Packed, + expectedAnnotation: "false", + }, + { + featureFlags: "SafeToEvict=true", + description: "Distributed, SafeToEvict=true", + scheduling: apis.Distributed, + expectedAnnotation: "false", + }, + { + featureFlags: "SafeToEvict=true", + description: "Packed with autoscaler annotation, SafeToEvict=true", + scheduling: apis.Packed, + setAnnotation: true, + expectedAnnotation: "true", + }, + { + featureFlags: "SafeToEvict=true", + description: "Distributed with autoscaler annotation, SafeToEvict=true", scheduling: apis.Distributed, setAnnotation: true, expectedAnnotation: "true", @@ -1335,9 +1366,13 @@ func TestGameServerPodAutoscalerAnnotations(t *testing.T) { fixture := &GameServer{ ObjectMeta: metav1.ObjectMeta{Name: "logan"}, Spec: GameServerSpec{Container: "sheep"}, + Status: GameServerStatus{Eviction: Eviction{Safe: EvictionSafeNever}}, } for _, tc := range testCases { t.Run(tc.description, func(t *testing.T) { + err := runtime.ParseFeatures(tc.featureFlags) + assert.NoError(t, err) + gs := fixture.DeepCopy() gs.Spec.Scheduling = tc.scheduling if tc.setAnnotation { diff --git a/pkg/cloudproduct/gke/gke.go b/pkg/cloudproduct/gke/gke.go index fce5ec8346..bbd9169b76 100644 --- a/pkg/cloudproduct/gke/gke.go +++ b/pkg/cloudproduct/gke/gke.go @@ -38,8 +38,9 @@ const ( noWorkloadDefaulter = "failed to get MutatingWebhookConfigurations/workload-defaulter.config.common-webhooks.networking.gke.io (error expected if not on GKE Autopilot)" hostPortAssignmentAnnotation = "autopilot.gke.io/host-port-assignment" - errPortPolicyMustBeDynamic = "PortPolicy must be Dynamic on GKE Autopilot" - errSchedulingMustBePacked = "Scheduling strategy must be Packed on GKE Autopilot" + errPortPolicyMustBeDynamic = "portPolicy must be Dynamic on GKE Autopilot" + errSchedulingMustBePacked = "scheduling strategy must be Packed on GKE Autopilot" + errEvictionSafeOnUpgradeInvalid = "eviction.safe OnUpgrade not supported on GKE Autopilot" ) var logger = runtime.NewLoggerWithSource("gke") @@ -127,6 +128,14 @@ func (*gkeAutopilot) ValidateGameServerSpec(gss *agonesv1.GameServerSpec) []meta Message: errSchedulingMustBePacked, }) } + // See SetEviction comment below for why we block EvictionSafeOnUpgrade. + if gss.Eviction.Safe == agonesv1.EvictionSafeOnUpgrade { + causes = append(causes, metav1.StatusCause{ + Type: metav1.CauseTypeFieldValueInvalid, + Field: "eviction.safe", + Message: errEvictionSafeOnUpgradeInvalid, + }) + } return causes } @@ -149,6 +158,37 @@ func podSpecSeccompUnconfined(podSpec *corev1.PodSpec) { podSpec.SecurityContext.SeccompProfile = &corev1.SeccompProfile{Type: corev1.SeccompProfileTypeUnconfined} } +// SetEviction sets disruption controls based on GameServer.Status.Eviction. For Autopilot: +// - Since the safe-to-evict pod annotation is not supported if "false", we delete it (if it's set +// to anything else, we allow it - Autopilot only rejects "false"). +// - OnUpgrade is not supported and rejected by validation above. Since we can't support +// safe-to-evict=false but can support a restrictive PDB, we can support Never and Always, but +// OnUpgrade doesn't make sense on Autopilot today. - an overly restrictive PDB prevents +// any sort of graceful eviction. +func (*gkeAutopilot) SetEviction(safe agonesv1.EvictionSafe, pod *corev1.Pod) error { + if !runtime.FeatureEnabled(runtime.FeatureSafeToEvict) { + return nil + } + if safeAnnotation := pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation]; safeAnnotation == agonesv1.False { + delete(pod.ObjectMeta.Annotations, agonesv1.PodSafeToEvictAnnotation) + } + if _, exists := pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel]; !exists { + switch safe { + case agonesv1.EvictionSafeAlways: + // For EvictionSafeAlways, we use a label value that does not match the + // agones-gameserver-safe-to-evict-false PDB. But we go ahead and label + // it, in case someone wants to adopt custom logic for this group of + // game servers. + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = agonesv1.True + case agonesv1.EvictionSafeNever: + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = agonesv1.False + default: + return errors.Errorf("eviction.safe == %s, which webhook should have rejected on Autopilot", safe) + } + } + return nil +} + type autopilotPortAllocator struct { minPort int32 maxPort int32 diff --git a/pkg/cloudproduct/gke/gke_test.go b/pkg/cloudproduct/gke/gke_test.go index 731645cd0a..540e3f946a 100644 --- a/pkg/cloudproduct/gke/gke_test.go +++ b/pkg/cloudproduct/gke/gke_test.go @@ -18,6 +18,7 @@ import ( "agones.dev/agones/pkg/apis" agonesv1 "agones.dev/agones/pkg/apis/agones/v1" + "agones.dev/agones/pkg/util/runtime" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -76,9 +77,10 @@ func TestSyncPodPortsToGameServer(t *testing.T) { func TestValidateGameServer(t *testing.T) { for name, tc := range map[string]struct { - ports []agonesv1.GameServerPort - scheduling apis.SchedulingStrategy - want []metav1.StatusCause + ports []agonesv1.GameServerPort + scheduling apis.SchedulingStrategy + safeToEvict agonesv1.EvictionSafe + want []metav1.StatusCause }{ "no ports => validated": {scheduling: apis.Packed}, "good ports => validated": { @@ -102,7 +104,8 @@ func TestValidateGameServer(t *testing.T) { Protocol: corev1.ProtocolTCP, }, }, - scheduling: apis.Packed, + safeToEvict: agonesv1.EvictionSafeAlways, + scheduling: apis.Packed, }, "bad policy => fails validation": { ports: []agonesv1.GameServerPort{ @@ -125,23 +128,29 @@ func TestValidateGameServer(t *testing.T) { Protocol: corev1.ProtocolUDP, }, }, - scheduling: apis.Distributed, + safeToEvict: agonesv1.EvictionSafeOnUpgrade, + scheduling: apis.Distributed, want: []metav1.StatusCause{ { Type: "FieldValueInvalid", - Message: "PortPolicy must be Dynamic on GKE Autopilot", + Message: "portPolicy must be Dynamic on GKE Autopilot", Field: "bad-udp.portPolicy", }, { Type: "FieldValueInvalid", - Message: "PortPolicy must be Dynamic on GKE Autopilot", + Message: "portPolicy must be Dynamic on GKE Autopilot", Field: "another-bad-udp.portPolicy", }, { Type: "FieldValueInvalid", - Message: "Scheduling strategy must be Packed on GKE Autopilot", + Message: "scheduling strategy must be Packed on GKE Autopilot", Field: "scheduling", }, + { + Type: "FieldValueInvalid", + Message: "eviction.safe OnUpgrade not supported on GKE Autopilot", + Field: "eviction.safe", + }, }, }, } { @@ -149,6 +158,7 @@ func TestValidateGameServer(t *testing.T) { causes := (&gkeAutopilot{}).ValidateGameServerSpec(&agonesv1.GameServerSpec{ Ports: tc.ports, Scheduling: tc.scheduling, + Eviction: agonesv1.Eviction{Safe: tc.safeToEvict}, }) require.Equal(t, tc.want, causes) }) @@ -197,6 +207,103 @@ func TestPodSeccompUnconfined(t *testing.T) { } } +func TestSetSafeToEvict(t *testing.T) { + runtime.FeatureTestMutex.Lock() + defer runtime.FeatureTestMutex.Unlock() + + emptyPodAnd := func(f func(*corev1.Pod)) *corev1.Pod { + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{}, + Labels: map[string]string{}, + }, + } + f(pod) + return pod + } + for desc, tc := range map[string]struct { + featureFlags string + safeToEvict agonesv1.EvictionSafe + pod *corev1.Pod + wantPod *corev1.Pod + wantErr bool + }{ + "SafeToEvict feature gate disabled => no change": { + featureFlags: "SafeToEvict=false", + // intentionally leave pod nil, it'll crash if anything's touched. + }, + "SafeToEvict: Always, no incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: agonesv1.EvictionSafeAlways, + pod: emptyPodAnd(func(*corev1.Pod) {}), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = agonesv1.True + }), + }, + "SafeToEvict: Never, no incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: agonesv1.EvictionSafeNever, + pod: emptyPodAnd(func(*corev1.Pod) {}), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = agonesv1.False + }), + }, + "SafeToEvict: OnUpgrade => error": { + featureFlags: "SafeToEvict=true", + safeToEvict: agonesv1.EvictionSafeOnUpgrade, + pod: emptyPodAnd(func(*corev1.Pod) {}), + wantErr: true, + }, + "SafeToEvict: Always, incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: agonesv1.EvictionSafeAlways, + pod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = "just don't touch, ok?" + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = "seriously, leave it" + }), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = "just don't touch, ok?" + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = "seriously, leave it" + }), + }, + "SafeToEvict: Never, incoming labels/annotations": { + featureFlags: "SafeToEvict=true", + safeToEvict: agonesv1.EvictionSafeNever, + pod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = "a passthrough" + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = "or is it passthru?" + }), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = "a passthrough" + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = "or is it passthru?" + }), + }, + "SafeToEvict: Never, but safe-to-evict pod annotation set to false": { + featureFlags: "SafeToEvict=true", + safeToEvict: agonesv1.EvictionSafeNever, + pod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Annotations[agonesv1.PodSafeToEvictAnnotation] = agonesv1.False + }), + wantPod: emptyPodAnd(func(pod *corev1.Pod) { + pod.ObjectMeta.Labels[agonesv1.SafeToEvictLabel] = agonesv1.False + }), + }, + } { + t.Run(desc, func(t *testing.T) { + err := runtime.ParseFeatures(tc.featureFlags) + assert.NoError(t, err) + + err = (&gkeAutopilot{}).SetEviction(tc.safeToEvict, tc.pod) + if tc.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tc.wantPod, tc.pod) + }) + } +} + func TestAutopilotPortAllocator(t *testing.T) { for name, tc := range map[string]struct { ports []agonesv1.GameServerPort diff --git a/site/content/en/docs/Reference/agones_crd_api_reference.html b/site/content/en/docs/Reference/agones_crd_api_reference.html index 5b877adcee..014603abbe 100644 --- a/site/content/en/docs/Reference/agones_crd_api_reference.html +++ b/site/content/en/docs/Reference/agones_crd_api_reference.html @@ -3029,6 +3029,9 @@
Packages:
Package v1 is the v1 version of the API.
Resource Types: --
Fleet is the data structure for a Fleet resource
+GameServerAllocationPolicy is the Schema for the gameserverallocationpolicies API
-agones.dev/v1
+multicluster.agones.dev/v1
|
@@ -3081,7 +3077,7 @@ Fleet |
+GameServerAllocationPolicy |
|||||||
@@ -3101,8 +3097,8 @@ Fleet
spec
-
-FleetSpec
+
+GameServerAllocationPolicySpec
|
|||||||||
-status
-
-
-FleetStatus
-
-
- |
-- | -
-
GameServer is the data structure for a GameServer resource.
-It is worth noting that while there is a GameServerStatus
Status entry for the GameServer
, it is not
-defined as a subresource - unlike Fleet
and other Agones resources.
-This is so that we can retain the ability to change multiple aspects of a GameServer
in a single atomic operation,
-which is particularly useful for operations such as allocation.
+
ClusterConnectionInfo defines the connection information for a cluster
-apiVersion
-string |
-
-
-agones.dev/v1
-
+clusterName
+
+string
+
|
-||||||||||||||||||
-kind
-string
+Optional: the name of the targeted cluster |
-GameServer |
||||||||||||||||||
-metadata
+allocationEndpoints
-
-Kubernetes meta/v1.ObjectMeta
-
+[]string
|
-Refer to the Kubernetes API documentation for the fields of the
-metadata field.
+The endpoints for the allocator service in the targeted cluster. +If the AllocationEndpoints is not set, the allocation happens on local cluster. +If there are multiple endpoints any of the endpoints that can handle allocation request should suffice |
||||||||||||||||||
-spec
+secretName
-
-GameServerSpec
-
+string
|
- - -
ConnectionInfoIterator +++ ConnectionInfoIterator an iterator on ClusterConnectionInfo + +
GameServerAllocationPolicySpec +++(Appears on: +GameServerAllocationPolicy) + ++ GameServerAllocationPolicySpec defines the desired state of GameServerAllocationPolicy + +
| ||||||||||||||||||
+weight
+
+int
+
+ |
+|||||||||||||||||||
-status
+connectionInfo
-
-GameServerStatus
+
+ClusterConnectionInfo
|
@@ -3342,12 +3329,23 @@
+
Package v1 is the v1 version of the API.
+ +Resource Types: +-
GameServerSet is the data structure for a set of GameServers. -This matches philosophically with the relationship between -Deployments and ReplicaSets
+Fleet is the data structure for a Fleet resource
GameServerSet |
+Fleet |
|
@@ -3392,8 +3390,8 @@ GameServerSet
spec
-
-GameServerSetSpec
+
+FleetSpec
|
- Replicas are the number of GameServers that should be in this set +Replicas are the number of GameServers that should be in this set. Defaults to 0. |
|
-scheduling
+strategy
-agones.dev/agones/pkg/apis.SchedulingStrategy
+
+Kubernetes apps/v1.DeploymentStrategy
+
|
- Scheduling strategy. Defaults to “Packed”. +Deployment strategy |
|
-template
+scheduling
-
-GameServerTemplateSpec
+agones.dev/agones/pkg/apis.SchedulingStrategy
+
+ |
+
+ Scheduling strategy. Defaults to “Packed”. + |
+|
+template
+
+
+GameServerTemplateSpec
|
- Template the GameServer template to apply for this GameServerSet +Template the GameServer template to apply for this Fleet |
status
-
-GameServerSetStatus
+
+FleetStatus
-(Appears on: -FleetStatus, -GameServerSetStatus) -
--
AggregatedPlayerStatus stores total player tracking values
+GameServer is the data structure for a GameServer resource.
+It is worth noting that while there is a GameServerStatus
Status entry for the GameServer
, it is not
+defined as a subresource - unlike Fleet
and other Agones resources.
+This is so that we can retain the ability to change multiple aspects of a GameServer
in a single atomic operation,
+which is particularly useful for operations such as allocation.
-count
-
-int64
-
+apiVersion
+string |
+
+
+agones.dev/v1
+
|
+
+kind
+string
|
+GameServer |
-capacity
+metadata
-int64
+
+Kubernetes meta/v1.ObjectMeta
+
|
+Refer to the Kubernetes API documentation for the fields of the
+metadata field.
|
-(Appears on: -Fleet) -
--
FleetSpec is the spec for a Fleet
- -Field | -Description | -
+spec
+
+
+GameServerSpec
+
+
+ |
+
+ + +
FleetStatus ---(Appears on: -Fleet, -FleetAutoscaleRequest) - -- FleetStatus is the status of a Fleet - -
|
---|---|
-players
+status
-
-AggregatedPlayerStatus
+
+GameServerStatus
|
-(Optional)
- [Stage:Alpha] -[FeatureFlag:PlayerTracking] -Players are the current total player capacity and count for this Fleet |
-(Appears on: -GameServerSpec) -
--
GameServerPort defines a set of Ports that -are to be exposed via the GameServer
+GameServerSet is the data structure for a set of GameServers. +This matches philosophically with the relationship between +Deployments and ReplicaSets
-name
-
-string
-
+apiVersion
+string |
+
+
+agones.dev/v1
+
|
+
- Name is the descriptive name of the port +kind
+string
|
+GameServerSet |
-portPolicy
+metadata
-
-PortPolicy
+
+Kubernetes meta/v1.ObjectMeta
|
- PortPolicy defines the policy for how the HostPort is populated.
-Dynamic port will allocate a HostPort within the selected MIN_PORT and MAX_PORT range passed to the controller
-at installation time.
-When metadata field.
|
-container
+spec
-string
+
+GameServerSetSpec
+
|
-(Optional)
- Container is the name of the container on which to open the port. Defaults to the game server container. - |
-
-containerPort
+replicas
int32
|
- ContainerPort is the port that is being opened on the specified container’s process +Replicas are the number of GameServers that should be in this set |
-hostPort
+scheduling
-int32
+agones.dev/agones/pkg/apis.SchedulingStrategy
|
- HostPort the port exposed on the host for clients to connect to +Scheduling strategy. Defaults to “Packed”. |
-protocol
+template
-
-Kubernetes core/v1.Protocol
+
+GameServerTemplateSpec
|
- Protocol is the network protocol being used. Defaults to UDP. TCP and TCPUDP are other options. +Template the GameServer template to apply for this GameServerSet |
-(Appears on: -GameServerSet) + + +
status
+
+
+GameServerSetStatus
+
+
++(Appears on: +FleetStatus, +GameServerSetStatus)
-
GameServerSetSpec the specification for GameServerSet
+AggregatedPlayerStatus stores total player tracking values
+ +Field | +Description | +
---|---|
+count
+
+int64
+
+ |
++ | +
+capacity
+
+int64
+
+ |
++ | +
+(Appears on: +GameServerSpec, +GameServerStatus) +
++
Eviction specifies the eviction tolerance of the GameServer
+ +Field | +Description | +
---|---|
+safe
+
+
+EvictionSafe
+
+
+ |
+
+ (Alpha, SafeToEvict feature flag) +Game server supports termination via SIGTERM: +- Always: Allow eviction for both Cluster Autoscaler and node drain for upgrades +- OnUpgrade: Allow eviction for upgrades alone +- Never (default): Pod should run to completion + |
+
string
alias)+(Appears on: +Eviction) +
++
EvictionSafe specified whether the game server supports termination via SIGTERM
+ ++(Appears on: +Fleet) +
++
FleetSpec is the spec for a Fleet
- Replicas are the number of GameServers that should be in this set +Replicas are the number of GameServers that should be in this set. Defaults to 0. + |
+
+|
+strategy
+
+
+Kubernetes apps/v1.DeploymentStrategy
+
+
+ |
+
+ Deployment strategy |
- Template the GameServer template to apply for this GameServerSet +Template the GameServer template to apply for this Fleet |
(Appears on: -GameServerSet) +Fleet, +FleetAutoscaleRequest)
-
GameServerSetStatus is the status of a GameServerSet
+FleetStatus is the status of a Fleet
- Replicas is the total number of current GameServer replicas +Replicas the total number of current GameServer replicas |
|
- ReadyReplicas is the number of Ready GameServer replicas +ReadyReplicas are the number of Ready GameServer replicas |
|
- ReservedReplicas is the number of Reserved GameServer replicas +ReservedReplicas are the total number of Reserved GameServer replicas in this fleet. +Reserved instances won’t be deleted on scale down, but won’t cause an autoscaler to scale up. |
|
- AllocatedReplicas is the number of Allocated GameServer replicas - |
-|
-shutdownReplicas
-
-int32
-
- |
-
- ShutdownReplicas is the number of Shutdown GameServers replicas +AllocatedReplicas are the number of Allocated GameServer replicas |
(Appears on: -GameServer, -GameServerTemplateSpec) +GameServerSpec)
-
GameServerSpec is the spec for a GameServer resource
+GameServerPort defines a set of Ports that +are to be exposed via the GameServer
-container
+name
string
|
- Container specifies which Pod container is the game server. Only required if there is more than one -container defined - |
-
-ports
-
-
-[]GameServerPort
-
-
- |
-
- Ports are the array of ports that can be exposed via the game server +Name is the descriptive name of the port |
-health
+portPolicy
-
-Health
+
+PortPolicy
|
- Health configures health checking +PortPolicy defines the policy for how the HostPort is populated.
+Dynamic port will allocate a HostPort within the selected MIN_PORT and MAX_PORT range passed to the controller
+at installation time.
+When |
-scheduling
+container
-agones.dev/agones/pkg/apis.SchedulingStrategy
+string
|
- Scheduling strategy. Defaults to “Packed” +(Optional) +Container is the name of the container on which to open the port. Defaults to the game server container. |
-sdkServer
+containerPort
-
-SdkServer
-
+int32
|
- SdkServer specifies parameters for the Agones SDK Server sidecar container +ContainerPort is the port that is being opened on the specified container’s process |
-template
+hostPort
-
-Kubernetes core/v1.PodTemplateSpec
-
+int32
|
- Template describes the Pod that will be created for the GameServer +HostPort the port exposed on the host for clients to connect to |
-players
+protocol
-
-PlayersSpec
+
+Kubernetes core/v1.Protocol
|
-(Optional)
- (Alpha, PlayerTracking feature flag) Players provides the configuration for player tracking features. +Protocol is the network protocol being used. Defaults to UDP. TCP and TCPUDP are other options. |
string
alias)-(Appears on: -GameServerSelector, -GameServerStatus) -
--
GameServerState is the state for the GameServer
- -(Appears on: -GameServer) +GameServerSet)
-
GameServerStatus is the status for a GameServer resource
+GameServerSetSpec the specification for GameServerSet
-state
+replicas
-
-GameServerState
-
+int32
|
- GameServerState is the current state of a GameServer, e.g. Creating, Starting, Ready, etc +Replicas are the number of GameServers that should be in this set |
-ports
+scheduling
-
-[]GameServerStatusPort
-
+agones.dev/agones/pkg/apis.SchedulingStrategy
|
+ Scheduling strategy. Defaults to “Packed”. |
-address
+template
-string
+
+GameServerTemplateSpec
+
|
+ Template the GameServer template to apply for this GameServerSet |
-nodeName
-
-string
-
+ |
+(Appears on: +GameServerSet) +
++
GameServerSetStatus is the status of a GameServerSet
+ +Field | +Description | +
---|---|
+replicas
+
+int32
+
|
+ Replicas is the total number of current GameServer replicas |
-reservedUntil
+readyReplicas
-
-Kubernetes meta/v1.Time
-
+int32
|
+ ReadyReplicas is the number of Ready GameServer replicas |
-players
+reservedReplicas
-
-PlayerStatus
-
+int32
|
-(Optional)
- [Stage:Alpha] -[FeatureFlag:PlayerTracking] +ReservedReplicas is the number of Reserved GameServer replicas |
-(Appears on: -GameServerAllocationStatus, -GameServerStatus) -
--
GameServerStatusPort shows the port that was allocated to a -GameServer.
- -Field | -Description | +
+allocatedReplicas
+
+int32
+
+ |
+
+ AllocatedReplicas is the number of Allocated GameServer replicas + |
---|---|---|---|
-name
+shutdownReplicas
-string
+int32
|
+ ShutdownReplicas is the number of Shutdown GameServers replicas |
||
-port
+players
-int32
+
+AggregatedPlayerStatus
+
|
+(Optional)
+ [Stage:Alpha] +[FeatureFlag:PlayerTracking] +Players is the current total player capacity and count for this GameServerSet |
(Appears on: -FleetSpec, -GameServerSetSpec) +GameServer, +GameServerTemplateSpec)
-
GameServerTemplateSpec is a template for GameServers
+GameServerSpec is the spec for a GameServer resource
-metadata
-
-
-Kubernetes meta/v1.ObjectMeta
-
-
- |
-
-Refer to the Kubernetes API documentation for the fields of the
-metadata field.
- |
-|
-spec
-
-
-GameServerSpec
-
-
- |
-
- - -
| |
+eviction
+
+
+Eviction
+
+
+ |
+
+(Optional)
+ (Alpha, SafeToEvict feature flag) Eviction specifies the eviction tolerance of the GameServer. Defaults to “Never”. |
string
alias)+(Appears on: +GameServerSelector, +GameServerStatus) +
++
GameServerState is the state for the GameServer
+ +(Appears on: -GameServerSpec) +GameServer)
-
Health configures health checking on the GameServer
+GameServerStatus is the status for a GameServer resource
-disabled
+state
-bool
+
+GameServerState
+
|
- Disabled is whether health checking is disabled or not +GameServerState is the current state of a GameServer, e.g. Creating, Starting, Ready, etc |
-periodSeconds
+ports
-int32
+
+[]GameServerStatusPort
+
|
- PeriodSeconds is the number of seconds each health ping has to occur in |
-failureThreshold
+address
-int32
+string
|
- FailureThreshold how many failures in a row constitutes unhealthy |
-initialDelaySeconds
+nodeName
-int32
+string
|
- InitialDelaySeconds initial delay before checking health |
-(Appears on: -GameServerStatus) -
--
PlayerStatus stores the current player capacity values
- -Field | -Description | -
---|---|
-count
+reservedUntil
-int64
+
+Kubernetes meta/v1.Time
+
|
@@ -4362,34 +4438,44 @@ PlayerStatus |
-capacity
+players
-int64
+
+PlayerStatus
+
|
+(Optional)
+ [Stage:Alpha] +[FeatureFlag:PlayerTracking] |
-ids
+eviction
-[]string
+
+Eviction
+
|
+ (Alpha, SafeToEvict feature flag) Eviction specifies the eviction tolerance of the GameServer. |
(Appears on: -GameServerSpec) +GameServerAllocationStatus, +GameServerStatus)
-
PlayersSpec tracks the initial player capacity
+GameServerStatusPort shows the port that was allocated to a +GameServer.
-initialCapacity
+name
-int64
+string
+
+ |
++ | +
+port
+
+int32
|
@@ -4411,23 +4507,15 @@ PlayersSpec |
string
alias)-(Appears on: -GameServerPort) -
--
PortPolicy is the port policy for the GameServer
- -(Appears on: -GameServerSpec) +FleetSpec, +GameServerSetSpec)
-
SdkServer specifies parameters for the Agones SDK Server sidecar container
+GameServerTemplateSpec is a template for GameServers
-logLevel
+metadata
-
-SdkServerLogLevel
+
+Kubernetes meta/v1.ObjectMeta
|
- LogLevel for SDK server (sidecar) logs. Defaults to “Info” +Refer to the Kubernetes API documentation for the fields of the +metadata field.
|
||||||||||||||
-grpcPort
+spec
-int32
+
+GameServerSpec
+
|
- GRPCPort is the port on which the SDK Server binds the gRPC server to accept incoming connections - |
-
-httpPort
+container
-int32
+string
|
- HTTPPort is the port on which the SDK Server binds the HTTP gRPC gateway server to accept incoming connections - |
-
string
alias)-(Appears on: -SdkServer) -
--
SdkServerLogLevel is the log level for SDK server (sidecar) logs
- --
Package v1 is the v1 version of the API.
- -Resource Types: - --
GameServerAllocation is the data structure for allocating against a set of
-GameServers, defined selectors
selectors
Field | -Description | -||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
-apiVersion
-string |
-
-
-allocation.agones.dev/v1
-
- |
-||||||||||||
-kind
-string
+Container specifies which Pod container is the game server. Only required if there is more than one +container defined |
-GameServerAllocation |
||||||||||||
-metadata
+ports
-
-Kubernetes meta/v1.ObjectMeta
+
+[]GameServerPort
|
-Refer to the Kubernetes API documentation for the fields of the
-metadata field.
+Ports are the array of ports that can be exposed via the game server |
||||||||||||
-spec
-
-
-GameServerAllocationSpec
-
-
- |
-
- - -
|
||||||||||||
-status
-
-
-GameServerAllocationStatus
-
-
- |
-- | -
(Appears on: -GameServerAllocation) +GameServerSpec)
-
GameServerAllocationSpec is the spec for a GameServerAllocation
+Health configures health checking on the GameServer
-multiClusterSetting
-
-
-MultiClusterSetting
-
-
- |
-
- MultiClusterPolicySelector if specified, multi-cluster policies are applied. -Otherwise, allocation will happen locally. - |
-
-required
-
-
-GameServerSelector
-
-
- |
-
- Deprecated: use field Selectors instead. If Selectors is set, this field is ignored. -Required is the GameServer selector from which to choose GameServers from. -Defaults to all GameServers. - |
-
-preferred
+disabled
-
-[]GameServerSelector
-
+bool
|
- Deprecated: use field Selectors instead. If Selectors is set, this field is ignored.
-Preferred is an ordered list of preferred GameServer selectors
-that are optional to be fulfilled, but will be searched before the Disabled is whether health checking is disabled or not |
-selectors
+periodSeconds
-
-[]GameServerSelector
-
+int32
|
- Ordered list of GameServer label selectors. -If the first selector is not matched, the selection attempts the second selector, and so on. -This is useful for things like smoke testing of new game servers. -Note: This field can only be set if neither Required or Preferred is set. +PeriodSeconds is the number of seconds each health ping has to occur in |
-scheduling
+failureThreshold
-agones.dev/agones/pkg/apis.SchedulingStrategy
+int32
|
- Scheduling strategy. Defaults to “Packed”. +FailureThreshold how many failures in a row constitutes unhealthy |
-metadata
+initialDelaySeconds
-
-MetaPatch
-
+int32
|
- MetaPatch is optional custom metadata that is added to the game server at allocation -You can use this to tell the server necessary session data +InitialDelaySeconds initial delay before checking health |
string
alias)-(Appears on: -GameServerAllocationStatus) -
--
GameServerAllocationState is the Allocation state
- -(Appears on: -GameServerAllocation) +GameServerStatus)
-
GameServerAllocationStatus is the status for an GameServerAllocation resource
+PlayerStatus stores the current player capacity values
-state
+count
-
-GameServerAllocationState
-
+int64
|
- GameServerState is the current state of an GameServerAllocation, e.g. Allocated, or UnAllocated |
-gameServerName
-
-string
-
- |
-- | -
-ports
+capacity
-
-[]GameServerStatusPort
-
+int64
|
@@ -4825,47 +4762,62 @@ GameServerAllocatio |
-address
+ids
-string
+[]string
|
+(Appears on: +GameServerSpec) +
++
PlayersSpec tracks the initial player capacity
+ +
-nodeName
-
-string
-
- |
-- | +Field | +Description |
---|---|---|---|
-source
+initialCapacity
-string
+int64
|
- If the allocation is from a remote cluster, Source is the endpoint of the remote agones-allocator. -Otherwise, Source is “local” |
string
alias)+(Appears on: +GameServerPort) +
++
PortPolicy is the port policy for the GameServer
+ +(Appears on: -GameServerAllocationSpec) +GameServerSpec)
-
GameServerSelector contains all the filter options for selecting -a GameServer for allocation.
+SdkServer specifies parameters for the Agones SDK Server sidecar container
-LabelSelector
+logLevel
-
-Kubernetes meta/v1.LabelSelector
+
+SdkServerLogLevel
|
-
-(Members of See: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +LogLevel for SDK server (sidecar) logs. Defaults to “Info” |
-gameServerState
+grpcPort
-
-GameServerState
-
+int32
|
-(Optional)
- [Stage:Beta] -[FeatureFlag:StateAllocationFilter] -GameServerState specifies which State is the filter to be used when attempting to retrieve a GameServer -via Allocation. Defaults to “Ready”. The only other option is “Allocated”, which can be used in conjunction with -label/annotation/player selectors to retrieve an already Allocated GameServer. +GRPCPort is the port on which the SDK Server binds the gRPC server to accept incoming connections |
-players
+httpPort
-
-PlayerSelector
-
+int32
|
-(Optional)
- [Stage:Alpha] -[FeatureFlag:PlayerAllocationFilter] -Players provides a filter on minimum and maximum values for player capacity when retrieving a GameServer -through Allocation. Defaults to no limits. +HTTPPort is the port on which the SDK Server binds the HTTP gRPC gateway server to accept incoming connections |
string
alias)(Appears on: -GameServerAllocationSpec) +SdkServer)
-
MetaPatch is the metadata used to patch the GameServer metadata on allocation
+SdkServerLogLevel is the log level for SDK server (sidecar) logs
+ ++
Package v1 is the v1 version of the API.
+ +Resource Types: + ++
GameServerAllocation is the data structure for allocating against a set of
+GameServers, defined selectors
selectors
-labels
+apiVersion
+string |
+
+
+allocation.agones.dev/v1
+
+ |
+||||||||||||||
+kind
+string
+ |
+GameServerAllocation |
+||||||||||||||
+metadata
-map[string]string
+
+Kubernetes meta/v1.ObjectMeta
+
|
+Refer to the Kubernetes API documentation for the fields of the
+metadata field.
|
||||||||||||||
-annotations
+spec
-map[string]string
+
+GameServerAllocationSpec
+
|
+ + +
MultiClusterSetting ---(Appears on: -GameServerAllocationSpec) - -- MultiClusterSetting specifies settings for multi-cluster allocation. - -
|
+||||||||||||||
+status
+
+
+GameServerAllocationStatus
|
@@ -5008,14 +5045,14 @@
(Appears on: -GameServerSelector) +GameServerAllocation)
-
PlayerSelector is the filter options for a GameServer based on player counts
+GameServerAllocationSpec is the spec for a GameServerAllocation
-minAvailable
+multiClusterSetting
-int64
+
+MultiClusterSetting
+
|
+ MultiClusterPolicySelector if specified, multi-cluster policies are applied. +Otherwise, allocation will happen locally. |
-maxAvailable
+required
-int64
+
+GameServerSelector
+
|
+ Deprecated: use field Selectors instead. If Selectors is set, this field is ignored. +Required is the GameServer selector from which to choose GameServers from. +Defaults to all GameServers. |
-
Package v1 is the v1 version of the API.
- -Resource Types: - --
FleetAutoscaler is the data structure for a FleetAutoscaler resource
- -Field | -Description | +
+preferred
+
+
+[]GameServerSelector
+
+
+ |
+
+ Deprecated: use field Selectors instead. If Selectors is set, this field is ignored.
+Preferred is an ordered list of preferred GameServer selectors
+that are optional to be fulfilled, but will be searched before the |
---|---|---|---|
-apiVersion
-string |
+
-
-autoscaling.agones.dev/v1
-
+Ordered list of GameServer label selectors. +If the first selector is not matched, the selection attempts the second selector, and so on. +This is useful for things like smoke testing of new game servers. +Note: This field can only be set if neither Required or Preferred is set. |
||
-kind
-string
+scheduling
+
+agones.dev/agones/pkg/apis.SchedulingStrategy
+
+ |
+
+ Scheduling strategy. Defaults to “Packed”. |
-FleetAutoscaler |
|
metadata
-
-Kubernetes meta/v1.ObjectMeta
+
+MetaPatch
|
-Refer to the Kubernetes API documentation for the fields of the
-metadata field.
+MetaPatch is optional custom metadata that is added to the game server at allocation +You can use this to tell the server necessary session data |
string
alias)+(Appears on: +GameServerAllocationStatus) +
++
GameServerAllocationState is the Allocation state
+ ++(Appears on: +GameServerAllocation) +
++
GameServerAllocationStatus is the status for an GameServerAllocation resource
+ +Field | +Description | +|||||
---|---|---|---|---|---|---|
-spec
+state
-
-FleetAutoscalerSpec
+
+GameServerAllocationState
|
- - -
| |||||
+nodeName
+
+string
+
+ |
+||||||
-status
+source
-
-FleetAutoscalerStatus
-
+string
|
+ If the allocation is from a remote cluster, Source is the endpoint of the remote agones-allocator. +Otherwise, Source is “local” |
(Appears on: -FleetAutoscalerPolicy) +GameServerAllocationSpec)
-
BufferPolicy controls the desired behavior of the buffer policy.
+GameServerSelector contains all the filter options for selecting +a GameServer for allocation.
-maxReplicas
+LabelSelector
-int32
+
+Kubernetes meta/v1.LabelSelector
+
|
- MaxReplicas is the maximum amount of replicas that the fleet may have. -It must be bigger than both MinReplicas and BufferSize +
+(Members of See: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ |
-minReplicas
+gameServerState
-int32
+
+GameServerState
+
|
- MinReplicas is the minimum amount of replicas that the fleet must have -If zero, it is ignored. -If non zero, it must be smaller than MaxReplicas and bigger than BufferSize +(Optional) +[Stage:Beta] +[FeatureFlag:StateAllocationFilter] +GameServerState specifies which State is the filter to be used when attempting to retrieve a GameServer +via Allocation. Defaults to “Ready”. The only other option is “Allocated”, which can be used in conjunction with +label/annotation/player selectors to retrieve an already Allocated GameServer. |
-bufferSize
+players
-k8s.io/apimachinery/pkg/util/intstr.IntOrString
+
+PlayerSelector
+
|
- BufferSize defines how many replicas the autoscaler tries to have ready all the time -Value can be an absolute number (ex: 5) or a percentage of desired gs instances (ex: 15%) -Absolute number is calculated from percentage by rounding up. -Example: when this is set to 20%, the autoscaler will make sure that 20% -of the fleet’s game server replicas are ready. When this is set to 20, -the autoscaler will make sure that there are 20 available game servers -Must be bigger than 0 -Note: by “ready” we understand in this case “non-allocated”; this is done to ensure robustness -and computation stability in different edge case (fleet just created, not enough -capacity in the cluster etc) +(Optional) +[Stage:Alpha] +[FeatureFlag:PlayerAllocationFilter] +Players provides a filter on minimum and maximum values for player capacity when retrieving a GameServer +through Allocation. Defaults to no limits. |
(Appears on: -FleetAutoscalerSync) +GameServerAllocationSpec)
-
FixedIntervalSync controls the desired behavior of the fixed interval based sync.
+MetaPatch is the metadata used to patch the GameServer metadata on allocation
-seconds
+labels
-int32
+map[string]string
+
+ |
++ | +
+annotations
+
+map[string]string
|
- Seconds defines how often we run fleet autoscaling in seconds |
(Appears on: -FleetAutoscaleReview) +GameServerAllocationSpec)
-
FleetAutoscaleRequest defines the request to webhook autoscaler endpoint
+MultiClusterSetting specifies settings for multi-cluster allocation.
-uid
-
-k8s.io/apimachinery/pkg/types.UID
-
- |
-
- UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are -otherwise identical (parallel requests, requests when earlier requests did not modify etc) -The UID is meant to track the round trip (request/response) between the Autoscaler and the WebHook, not the user request. -It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. - |
-
-name
-
-string
-
- |
-
- Name is the name of the Fleet being scaled - |
-
-namespace
+enabled
-string
+bool
|
- Namespace is the namespace associated with the request (if any). |
-status
+policySelector
-
-FleetStatus
+
+Kubernetes meta/v1.LabelSelector
|
- The Fleet’s status values |
(Appears on: -FleetAutoscaleReview) +GameServerSelector)
-
FleetAutoscaleResponse defines the response of webhook autoscaler endpoint
+PlayerSelector is the filter options for a GameServer based on player counts
-uid
-
-k8s.io/apimachinery/pkg/types.UID
-
- |
-
- UID is an identifier for the individual request/response. -This should be copied over from the corresponding FleetAutoscaleRequest. - |
-
-scale
+minAvailable
-bool
+int64
|
- Set to false if no scaling should occur to the Fleet |
-replicas
+maxAvailable
-int32
+int64
|
- The targeted replica count |
+
Package v1 is the v1 version of the API.
+ +Resource Types: + +-
FleetAutoscaleReview is passed to the webhook with a populated Request value, -and then returned with a populated Response.
+FleetAutoscaler is the data structure for a FleetAutoscaler resource
-request
+apiVersion
+string |
+
+
+autoscaling.agones.dev/v1
+
+ |
+
+kind
+string
+ |
+FleetAutoscaler |
+
+metadata
-
-FleetAutoscaleRequest
+
+Kubernetes meta/v1.ObjectMeta
|
+Refer to the Kubernetes API documentation for the fields of the
+metadata field.
|
-response
+spec
-
-FleetAutoscaleResponse
+
+FleetAutoscalerSpec
|
- | -
-(Appears on: -FleetAutoscalerSpec) -
--
FleetAutoscalerPolicy describes how to scale a fleet
- +Field | -Description | +
+fleetName
+
+string
+
+ |
++ |
---|---|---|---|
-type
+policy
-
-FleetAutoscalerPolicyType
+
+FleetAutoscalerPolicy
|
- Type of autoscaling policy. +Autoscaling policy |
||
-buffer
+sync
-
-BufferPolicy
+
+FleetAutoscalerSync
|
(Optional)
- Buffer policy config params. Present only if FleetAutoscalerPolicyType = Buffer. +[Stage:Beta] +[FeatureFlag:CustomFasSyncInterval] +Sync defines when FleetAutoscalers runs autoscaling + |
+
webhook
+status
-
-WebhookPolicy
+
+FleetAutoscalerStatus
Webhook policy config params. Present only if FleetAutoscalerPolicyType = Webhook.
string
alias)-(Appears on: -FleetAutoscalerPolicy) -
--
FleetAutoscalerPolicyType is the policy for autoscaling -for a given Fleet
- -(Appears on: -FleetAutoscaler) +FleetAutoscalerPolicy)
-
FleetAutoscalerSpec is the spec for a Fleet Scaler
+BufferPolicy controls the desired behavior of the buffer policy.
-fleetName
+maxReplicas
-string
+int32
|
+ MaxReplicas is the maximum amount of replicas that the fleet may have. +It must be bigger than both MinReplicas and BufferSize |
-policy
+minReplicas
-
-FleetAutoscalerPolicy
-
+int32
|
- Autoscaling policy +MinReplicas is the minimum amount of replicas that the fleet must have +If zero, it is ignored. +If non zero, it must be smaller than MaxReplicas and bigger than BufferSize |
-sync
+bufferSize
-
-FleetAutoscalerSync
-
+k8s.io/apimachinery/pkg/util/intstr.IntOrString
|
-(Optional)
- [Stage:Beta] -[FeatureFlag:CustomFasSyncInterval] -Sync defines when FleetAutoscalers runs autoscaling +BufferSize defines how many replicas the autoscaler tries to have ready all the time +Value can be an absolute number (ex: 5) or a percentage of desired gs instances (ex: 15%) +Absolute number is calculated from percentage by rounding up. +Example: when this is set to 20%, the autoscaler will make sure that 20% +of the fleet’s game server replicas are ready. When this is set to 20, +the autoscaler will make sure that there are 20 available game servers +Must be bigger than 0 +Note: by “ready” we understand in this case “non-allocated”; this is done to ensure robustness +and computation stability in different edge case (fleet just created, not enough +capacity in the cluster etc) |
(Appears on: -FleetAutoscaler) +FleetAutoscalerSync)
-
FleetAutoscalerStatus defines the current status of a FleetAutoscaler
+FixedIntervalSync controls the desired behavior of the fixed interval based sync.
-currentReplicas
+seconds
int32
|
- CurrentReplicas is the current number of gameserver replicas -of the fleet managed by this autoscaler, as last seen by the autoscaler +Seconds defines how often we run fleet autoscaling in seconds |
+(Appears on: +FleetAutoscaleReview) +
++
FleetAutoscaleRequest defines the request to webhook autoscaler endpoint
+ +Field | +Description | +
---|---|
-desiredReplicas
+uid
-int32
+k8s.io/apimachinery/pkg/types.UID
|
- DesiredReplicas is the desired number of gameserver replicas -of the fleet managed by this autoscaler, as last calculated by the autoscaler +UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are +otherwise identical (parallel requests, requests when earlier requests did not modify etc) +The UID is meant to track the round trip (request/response) between the Autoscaler and the WebHook, not the user request. +It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. |
-lastScaleTime
+name
-
-Kubernetes meta/v1.Time
-
+string
|
-(Optional)
- lastScaleTime is the last time the FleetAutoscaler scaled the attached fleet, +Name is the name of the Fleet being scaled |
-ableToScale
+namespace
-bool
+string
|
- AbleToScale indicates that we can access the target fleet +Namespace is the namespace associated with the request (if any). |
-scalingLimited
+status
-bool
+
+FleetStatus
+
|
- ScalingLimited indicates that the calculated scale would be above or below the range -defined by MinReplicas and MaxReplicas, and has thus been capped. +The Fleet’s status values |
(Appears on: -FleetAutoscalerSpec) +FleetAutoscaleReview)
-
FleetAutoscalerSync describes when to sync a fleet
+FleetAutoscaleResponse defines the response of webhook autoscaler endpoint
-type
+uid
-
-FleetAutoscalerSyncType
-
+k8s.io/apimachinery/pkg/types.UID
|
- Type of autoscaling sync. +UID is an identifier for the individual request/response. +This should be copied over from the corresponding FleetAutoscaleRequest. |
-fixedInterval
+scale
-
-FixedIntervalSync
-
+bool
|
-(Optional)
- FixedInterval config params. Present only if FleetAutoscalerSyncType = FixedInterval. +Set to false if no scaling should occur to the Fleet + |
+
+replicas
+
+int32
+
+ |
+
+ The targeted replica count |
string
alias)-(Appears on: -FleetAutoscalerSync) -
--
FleetAutoscalerSyncType is the sync strategy for a given Fleet
- --(Appears on: -FleetAutoscalerPolicy) -
--
WebhookPolicy controls the desired behavior of the webhook policy. -It contains the description of the webhook autoscaler service -used to form url which is accessible inside the cluster
+FleetAutoscaleReview is passed to the webhook with a populated Request value, +and then returned with a populated Response.
-url
-
-string
-
- |
-
-(Optional)
-
The Please note that using The scheme must be “https”; the URL must begin with “https://”. -A path is optional, and if present may be any string permissible in -a URL. You may use the path to pass an arbitrary string to the -webhook, for example, a cluster identifier. -Attempting to use a user or basic auth e.g. “user:password@” is not -allowed. Fragments (“#…”) and query parameters (“?…”) are not -allowed, either. - |
-
-service
+request
-
-Kubernetes admissionregistration/v1.ServiceReference
+
+FleetAutoscaleRequest
|
-(Optional)
-
If the webhook is running within the cluster, then you should use |
-caBundle
+response
-[]byte
+
+FleetAutoscaleResponse
+
|
-(Optional)
-
|
-
Package v1 is the v1 version of the API.
+(Appears on: +FleetAutoscalerSpec) -Resource Types: - --
GameServerAllocationPolicy is the Schema for the gameserverallocationpolicies API
+FleetAutoscalerPolicy describes how to scale a fleet
-apiVersion
-string |
-
-
-multicluster.agones.dev/v1
-
+type
+
+
+FleetAutoscalerPolicyType
+
+
|
-
-kind
-string
+Type of autoscaling policy. |
-GameServerAllocationPolicy |
-metadata
+buffer
-
-Kubernetes meta/v1.ObjectMeta
+
+BufferPolicy
|
-Refer to the Kubernetes API documentation for the fields of the
-metadata field.
+(Optional)
+Buffer policy config params. Present only if FleetAutoscalerPolicyType = Buffer. |
-spec
+webhook
-
-GameServerAllocationPolicySpec
+
+WebhookPolicy
|
- - +(Optional) + Webhook policy config params. Present only if FleetAutoscalerPolicyType = Webhook. + |
+
string
alias)+(Appears on: +FleetAutoscalerPolicy) +
++
FleetAutoscalerPolicyType is the policy for autoscaling +for a given Fleet
+ ++(Appears on: +FleetAutoscaler) +
++
FleetAutoscalerSpec is the spec for a Fleet Scaler
+Field | +Description | +
---|---|
-priority
+fleetName
-int32
+string
|
@@ -5846,39 +5913,43 @@ GameServerAllocat |
-weight
+policy
-int
+
+FleetAutoscalerPolicy
+
|
+ Autoscaling policy |
-connectionInfo
+sync
-
-ClusterConnectionInfo
+
+FleetAutoscalerSync
|
- | -
[Stage:Beta] +[FeatureFlag:CustomFasSyncInterval] +Sync defines when FleetAutoscalers runs autoscaling
-(Appears on: -GameServerAllocationPolicySpec) +FleetAutoscaler)
-
ClusterConnectionInfo defines the connection information for a cluster
+FleetAutoscalerStatus defines the current status of a FleetAutoscaler
-clusterName
+currentReplicas
-string
+int32
|
- Optional: the name of the targeted cluster +CurrentReplicas is the current number of gameserver replicas +of the fleet managed by this autoscaler, as last seen by the autoscaler |
-allocationEndpoints
+desiredReplicas
-[]string
+int32
|
- The endpoints for the allocator service in the targeted cluster. -If the AllocationEndpoints is not set, the allocation happens on local cluster. -If there are multiple endpoints any of the endpoints that can handle allocation request should suffice +DesiredReplicas is the desired number of gameserver replicas +of the fleet managed by this autoscaler, as last calculated by the autoscaler |
-secretName
+lastScaleTime
-string
+
+Kubernetes meta/v1.Time
+
|
- The name of the secret that contains TLS client certificates to connect the allocator server in the targeted cluster +(Optional) +lastScaleTime is the last time the FleetAutoscaler scaled the attached fleet, |
-namespace
+ableToScale
-string
+bool
|
- The cluster namespace from which to allocate gameservers +AbleToScale indicates that we can access the target fleet |
-serverCa
+scalingLimited
-[]byte
+bool
|
- The PEM encoded server CA, used by the allocator client to authenticate the remote server. +ScalingLimited indicates that the calculated scale would be above or below the range +defined by MinReplicas and MaxReplicas, and has thus been capped. |
-
ConnectionInfoIterator an iterator on ClusterConnectionInfo
+(Appears on: +FleetAutoscalerSpec) + ++
FleetAutoscalerSync describes when to sync a fleet
-currPriority
-
-int
-
- |
-
- currPriority Current priority index from the orderedPriorities - |
-
-orderedPriorities
-
-[]int32
-
- |
-
- orderedPriorities list of ordered priorities - |
-
-priorityToCluster
+type
-map[int32]map[string][]*agones.dev/agones/pkg/apis/multicluster/v1.GameServerAllocationPolicy
+
+FleetAutoscalerSyncType
+
|
- priorityToCluster Map of priority to cluster-policies map +Type of autoscaling sync. |
-clusterBlackList
+fixedInterval
-map[string]bool
+
+FixedIntervalSync
+
|
- clusterBlackList the cluster blacklist for the clusters that has already returned +(Optional) +FixedInterval config params. Present only if FleetAutoscalerSyncType = FixedInterval. |
string
alias)+(Appears on: +FleetAutoscalerSync) +
++
FleetAutoscalerSyncType is the sync strategy for a given Fleet
+ +(Appears on: -GameServerAllocationPolicy) +FleetAutoscalerPolicy)
-
GameServerAllocationPolicySpec defines the desired state of GameServerAllocationPolicy
+WebhookPolicy controls the desired behavior of the webhook policy. +It contains the description of the webhook autoscaler service +used to form url which is accessible inside the cluster
-priority
+url
-int32
+string
|
+(Optional)
+
The Please note that using The scheme must be “https”; the URL must begin with “https://”. +A path is optional, and if present may be any string permissible in +a URL. You may use the path to pass an arbitrary string to the +webhook, for example, a cluster identifier. +Attempting to use a user or basic auth e.g. “user:password@” is not +allowed. Fragments (“#…”) and query parameters (“?…”) are not +allowed, either. |
-weight
+service
-int
+
+Kubernetes admissionregistration/v1.ServiceReference
+
|
+(Optional)
+
If the webhook is running within the cluster, then you should use |
-connectionInfo
+caBundle
-
-ClusterConnectionInfo
-
+[]byte
|
+(Optional)
+
|