diff --git a/pkg/gameservers/controller.go b/pkg/gameservers/controller.go index c78ed0e1aa..e7ad9e8223 100644 --- a/pkg/gameservers/controller.go +++ b/pkg/gameservers/controller.go @@ -18,6 +18,7 @@ import ( "encoding/json" "fmt" "net" + "strconv" "sync" "time" @@ -55,6 +56,12 @@ import ( "k8s.io/client-go/util/workqueue" ) +const ( + sdkserverSidecarName = "agones-gameserver-sidecar" + grpcPortEnvVar = "AGONES_SDK_GRPC_PORT" + httpPortEnvVar = "AGONES_SDK_HTTP_PORT" +) + // Controller is a the main GameServer crd controller type Controller struct { baseLogger *logrus.Entry @@ -520,11 +527,9 @@ func (c *Controller) syncDevelopmentGameServer(gs *agonesv1.GameServer) (*agones // createGameServerPod creates the backing Pod for a given GameServer func (c *Controller) createGameServerPod(gs *agonesv1.GameServer) (*agonesv1.GameServer, error) { sidecar := c.sidecar(gs) - var pod *corev1.Pod pod, err := gs.Pod(sidecar) - - // this shouldn't happen, but if it does. if err != nil { + // this shouldn't happen, but if it does. c.loggerForGameServer(gs).WithError(err).Error("error creating pod from Game Server") gs, err = c.moveToErrorState(gs, err.Error()) return gs, err @@ -539,6 +544,7 @@ func (c *Controller) createGameServerPod(gs *agonesv1.GameServer) (*agonesv1.Gam } c.addGameServerHealthCheck(gs, pod) + c.addSDKServerEnvVars(gs, pod) c.loggerForGameServer(gs).WithField("pod", pod).Info("creating Pod for GameServer") pod, err = c.podGetter.Pods(gs.ObjectMeta.Namespace).Create(pod) @@ -563,7 +569,7 @@ func (c *Controller) createGameServerPod(gs *agonesv1.GameServer) (*agonesv1.Gam // sidecar creates the sidecar container for a given GameServer func (c *Controller) sidecar(gs *agonesv1.GameServer) corev1.Container { sidecar := corev1.Container{ - Name: "agones-gameserver-sidecar", + Name: sdkserverSidecarName, Image: c.sidecarImage, Env: []corev1.EnvVar{ { @@ -639,6 +645,51 @@ func (c *Controller) addGameServerHealthCheck(gs *agonesv1.GameServer, pod *core }) } +func (c *Controller) addSDKServerEnvVars(gs *agonesv1.GameServer, pod *corev1.Pod) { + for i, c := range pod.Spec.Containers { + if c.Name != sdkserverSidecarName { + sdkEnvVars := sdkEnvironmentVariables(gs) + if sdkEnvVars == nil { + // If a gameserver was created before 1.1 when we started defaulting the grpc and http ports, + // don't change the container spec. + continue + } + + // Filter out environment variables that have reserved names. + // From https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating + env := c.Env[:0] + for _, e := range c.Env { + if !reservedEnvironmentVariableName(e.Name) { + env = append(env, e) + } + } + c.Env = append(env, sdkEnvVars...) + pod.Spec.Containers[i] = c + } + } +} + +func reservedEnvironmentVariableName(name string) bool { + return name == grpcPortEnvVar || name == httpPortEnvVar +} + +func sdkEnvironmentVariables(gs *agonesv1.GameServer) []corev1.EnvVar { + var env []corev1.EnvVar + if gs.Spec.SdkServer.GRPCPort != 0 { + env = append(env, corev1.EnvVar{ + Name: grpcPortEnvVar, + Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort)), + }) + } + if gs.Spec.SdkServer.HTTPPort != 0 { + env = append(env, corev1.EnvVar{ + Name: httpPortEnvVar, + Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort)), + }) + } + return env +} + // syncGameServerStartingState looks for a pod that has been scheduled for this GameServer // and then sets the Status > Address and Ports values. func (c *Controller) syncGameServerStartingState(gs *agonesv1.GameServer) (*agonesv1.GameServer, error) { diff --git a/pkg/gameservers/controller_test.go b/pkg/gameservers/controller_test.go index 11692b41c9..92222b0c55 100644 --- a/pkg/gameservers/controller_test.go +++ b/pkg/gameservers/controller_test.go @@ -19,6 +19,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "testing" "agones.dev/agones/pkg/apis/agones" @@ -48,9 +49,7 @@ const ( nodeFixtureName = "node1" ) -var ( - GameServerKind = metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("GameServer")) -) +var GameServerKind = metav1.GroupVersionKind(agonesv1.SchemeGroupVersion.WithKind("GameServer")) func TestControllerSyncGameServer(t *testing.T) { t.Parallel() @@ -159,6 +158,7 @@ func runReconcileDeleteGameServer(t *testing.T, fixture *agonesv1.GameServer) { assert.Nil(t, err, fmt.Sprintf("Shouldn't be an error from syncGameServer: %+v", err)) assert.False(t, podAction, "Nothing should happen to a Pod") } + func TestControllerSyncGameServerWithDevIP(t *testing.T) { t.Parallel() @@ -1208,6 +1208,182 @@ func TestControllerAddGameServerHealthCheck(t *testing.T) { assert.Equal(t, fixture.Spec.Health.PeriodSeconds, probe.PeriodSeconds) } +func TestControllerAddSDKServerEnvVars(t *testing.T) { + + t.Run("legacy game server without ports set", func(t *testing.T) { + // For backwards compatibility, verify that no variables are set if the ports + // are not set on the game server. + c, _ := newFakeController() + gs := &agonesv1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "1234"}, + Spec: newSingleContainerSpec(), + } + gs.ApplyDefaults() + gs.Spec.SdkServer = agonesv1.SdkServer{} + pod, err := gs.Pod() + assert.Nil(t, err, "Error: %v", err) + before := pod.DeepCopy() + c.addSDKServerEnvVars(gs, pod) + assert.Equal(t, before, pod, "Error: pod unexpectedly modified. before = %v, after = %v", before, pod) + }) + + t.Run("game server without any environment", func(t *testing.T) { + c, _ := newFakeController() + gs := &agonesv1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "2345"}, + Spec: newSingleContainerSpec(), + } + gs.ApplyDefaults() + pod, err := gs.Pod() + assert.Nil(t, err, "Error: %v", err) + c.addSDKServerEnvVars(gs, pod) + assert.Len(t, pod.Spec.Containers, 1, "Expected 1 container, found %d", len(pod.Spec.Containers)) + assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))}) + assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))}) + }) + + t.Run("game server without any conflicting env vars", func(t *testing.T) { + c, _ := newFakeController() + gs := &agonesv1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "3456"}, + Spec: agonesv1.GameServerSpec{ + Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}}, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "container/image", + Env: []corev1.EnvVar{{Name: "one", Value: "value"}, {Name: "two", Value: "value"}}, + }, + }, + }, + }, + }, + } + gs.ApplyDefaults() + pod, err := gs.Pod() + assert.Nil(t, err, "Error: %v", err) + c.addSDKServerEnvVars(gs, pod) + assert.Len(t, pod.Spec.Containers, 1, "Expected 1 container, found %d", len(pod.Spec.Containers)) + assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))}) + assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))}) + }) + + t.Run("game server with conflicting env vars", func(t *testing.T) { + c, _ := newFakeController() + gs := &agonesv1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "4567"}, + Spec: agonesv1.GameServerSpec{ + Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}}, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container", + Image: "container/image", + Env: []corev1.EnvVar{{Name: grpcPortEnvVar, Value: "value"}, {Name: httpPortEnvVar, Value: "value"}}, + }, + }, + }, + }, + }, + } + gs.ApplyDefaults() + pod, err := gs.Pod() + assert.Nil(t, err, "Error: %v", err) + c.addSDKServerEnvVars(gs, pod) + assert.Len(t, pod.Spec.Containers, 1, "Expected 1 container, found %d", len(pod.Spec.Containers)) + assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))}) + assert.Contains(t, pod.Spec.Containers[0].Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))}) + }) + + t.Run("game server with multiple containers", func(t *testing.T) { + c, _ := newFakeController() + gs := &agonesv1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "5678"}, + Spec: agonesv1.GameServerSpec{ + Container: "container1", + Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}}, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container1", + Image: "container/gameserver", + }, + { + Name: "container2", + Image: "container/image2", + Env: []corev1.EnvVar{{Name: "one", Value: "value"}, {Name: "two", Value: "value"}}, + }, + { + Name: "container3", + Image: "container/image2", + Env: []corev1.EnvVar{{Name: grpcPortEnvVar, Value: "value"}, {Name: httpPortEnvVar, Value: "value"}}, + }, + }, + }, + }, + }, + } + gs.ApplyDefaults() + pod, err := gs.Pod() + assert.Nil(t, err, "Error: %v", err) + c.addSDKServerEnvVars(gs, pod) + for _, c := range pod.Spec.Containers { + assert.Contains(t, c.Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))}) + assert.Contains(t, c.Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))}) + } + }) + + t.Run("environment variables not applied to the sdkserver container", func(t *testing.T) { + c, _ := newFakeController() + gs := &agonesv1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: "gameserver", UID: "5678"}, + Spec: agonesv1.GameServerSpec{ + Container: "container1", + Ports: []agonesv1.GameServerPort{{ContainerPort: 7777}}, + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "container1", + Image: "container/gameserver", + }, + { + Name: "container2", + Image: "container/image2", + Env: []corev1.EnvVar{{Name: "one", Value: "value"}, {Name: "two", Value: "value"}}, + }, + { + Name: "container3", + Image: "container/image2", + Env: []corev1.EnvVar{{Name: grpcPortEnvVar, Value: "value"}, {Name: httpPortEnvVar, Value: "value"}}, + }, + }, + }, + }, + }, + } + gs.ApplyDefaults() + sidecar := c.sidecar(gs) + pod, err := gs.Pod(sidecar) + assert.Nil(t, err, "Error: %v", err) + c.addSDKServerEnvVars(gs, pod) + for _, c := range pod.Spec.Containers { + if c.Name == sdkserverSidecarName { + assert.NotContains(t, c.Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))}) + assert.NotContains(t, c.Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))}) + } else { + assert.Contains(t, c.Env, corev1.EnvVar{Name: grpcPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.GRPCPort))}) + assert.Contains(t, c.Env, corev1.EnvVar{Name: httpPortEnvVar, Value: strconv.Itoa(int(gs.Spec.SdkServer.HTTPPort))}) + } + } + }) + +} + func TestIsGameServerPod(t *testing.T) { t.Run("it is a game server pod", func(t *testing.T) { diff --git a/sdks/go/sdk.go b/sdks/go/sdk.go index 1678c62d56..475d324360 100644 --- a/sdks/go/sdk.go +++ b/sdks/go/sdk.go @@ -18,6 +18,8 @@ package sdk import ( "fmt" "io" + "os" + "strconv" "time" "agones.dev/agones/pkg/sdk" @@ -26,7 +28,7 @@ import ( "google.golang.org/grpc" ) -const port = 59357 +const defaultPort = 59357 // GameServerCallback is a function definition to be called // when a GameServer CRD has been changed @@ -39,11 +41,32 @@ type SDK struct { health sdk.SDK_HealthClient } +func port() int { + portStr := os.Getenv("AGONES_SDK_GRPC_PORT") + if portStr == "" { + // Environment variable is not set; use the default port. + _, _ = fmt.Fprintf(os.Stderr, "Environment variable AGONES_SDK_GRPC_PORT not defined, using default port %d\n", defaultPort) + return defaultPort + } + p, err := strconv.Atoi(portStr) + if err != nil { + // Environment variable cannot be parsed; use the default port. + _, _ = fmt.Fprintf(os.Stderr, "Unable to parse %q defined in AGONES_SDK_GRPC_PORT into an integer\n", portStr) + return defaultPort + } + if p < 1 || p > 65535 { + // Environment variable is not a valid port; use the default port. + _, _ = fmt.Fprintf(os.Stderr, "Invalid port %d defined in AGONES_SDK_GRPC_PORT. It must be between 1 and 65535\n", p) + return defaultPort + } + return p +} + // NewSDK starts a new SDK instance, and connects to // localhost on port 59357. Blocks until connection and handshake are made. // Times out after 30 seconds. func NewSDK() (*SDK, error) { - addr := fmt.Sprintf("localhost:%d", port) + addr := fmt.Sprintf("localhost:%d", port()) s := &SDK{ ctx: context.Background(), } @@ -130,10 +153,10 @@ func (s *SDK) WatchGameServer(f GameServerCallback) error { gs, err = stream.Recv() if err != nil { if err == io.EOF { - fmt.Println("gameserver event stream EOF received") + _, _ = fmt.Fprintln(os.Stderr, "gameserver event stream EOF received") return } - fmt.Printf("error watching GameServer: %s\n", err.Error()) + _, _ = fmt.Fprintf(os.Stderr, "error watching GameServer: %s\n", err.Error()) // This is to wait for the reconnection, and not peg the CPU at 100% time.Sleep(time.Second) continue diff --git a/site/content/en/docs/Guides/Client SDKs/_index.md b/site/content/en/docs/Guides/Client SDKs/_index.md index 72041b4a20..4e164e3020 100644 --- a/site/content/en/docs/Guides/Client SDKs/_index.md +++ b/site/content/en/docs/Guides/Client SDKs/_index.md @@ -32,6 +32,25 @@ This means that more languages can be supported in the future with minimal effor There is also [local development tooling]({{< relref "local.md" >}}) for working against the SDK locally, without having to spin up an entire Kubernetes infrastructure. +{{% feature publishVersion="1.1.0" %}} +## Connecting to the SDK Server + +Starting with Agones 1.1.0, the port that the SDK Server listens on for incoming gRPC or HTTP requests is +configurable. This provides flexibility in cases where the default port conflicts with a port that is needed +by the game server. + +Agones will automatically set the following environment variables on all game server containers: + +* `AGONES_SDK_GRPC_PORT`: The port where the gRPC server is listening +* `AGONES_SDK_HTTP_PORT`: The port where the grpc-gateway is listening + +The SDKs will automatically discover and connect to the gRPC port specified in the environment variable. + +If your game server requires using a REST client, it is advised to use the port from the environment variable, +otherwise your game server will not be able to contact the SDK server if it is configured to use a non-default port. + +{{% /feature %}} + ## Function Reference While each of the SDKs are canonical to their languages, they all have the following diff --git a/site/content/en/docs/Guides/Client SDKs/rest.md b/site/content/en/docs/Guides/Client SDKs/rest.md index fd832a5c99..5372a06f66 100644 --- a/site/content/en/docs/Guides/Client SDKs/rest.md +++ b/site/content/en/docs/Guides/Client SDKs/rest.md @@ -8,7 +8,17 @@ description: "This is the REST version of the Agones Game Server Client SDK. " Check the [Client SDK Documentation]({{< relref "_index.md" >}}) for more details on each of the SDK functions and how to run the SDK locally. +{{% feature expiryVersion="1.1.0" %}} The REST API can be accessed from `http://localhost:59358/` from the game server process. +{{% /feature %}} + +{{% feature publishVersion="1.1.0" %}} +The REST API can be accessed from `http://localhost:${AGONES_SDK_HTTP_PORT}/` from the game server process. +`AGONES_SDK_HTTP_PORT` is an environment variable automatically set for the game server process by Agones to +support binding the REST API to a dynamic port. It is advised to use the environment variable rather than a +hard coded port; otherwise your game server will not be able to contact the SDK server if it is configured to +use a non-default port. +{{% /feature %}} Generally the REST interface gets used if gRPC isn't well supported for a given language or platform. @@ -39,9 +49,16 @@ Call when the GameServer is ready to accept connections #### Example +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:59358/ready ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/ready +``` +{{% /feature %}} ### Health Send a Empty every d Duration to declare that this GameSever is healthy @@ -52,9 +69,16 @@ Send a Empty every d Duration to declare that this GameSever is healthy #### Example +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:59358/health ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/health +``` +{{% /feature %}} ### Shutdown @@ -66,9 +90,16 @@ Call when the GameServer session is over and it's time to shut down #### Example +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:59358/shutdown ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/shutdown +``` +{{% /feature %}} ### Set Label @@ -78,9 +109,16 @@ See the SDK [SetLabel]({{< ref "/docs/Guides/Client SDKs/_index.md#setlabel-key- #### Example +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -d '{"key": "foo", "value": "bar"}' -H "Content-Type: application/json" -X PUT http://localhost:59358/metadata/label ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -d '{"key": "foo", "value": "bar"}' -H "Content-Type: application/json" -X PUT http://localhost:${AGONES_SDK_HTTP_PORT}/metadata/label +``` +{{% /feature %}} ### Set Annotation @@ -88,9 +126,16 @@ Apply a Annotation with the prefix "agones.dev/sdk-" to the backing `GameServer` #### Example +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -d '{"key": "foo", "value": "bar"}' -H "Content-Type: application/json" -X PUT http://localhost:59358/metadata/annotation ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -d '{"key": "foo", "value": "bar"}' -H "Content-Type: application/json" -X PUT http://localhost:${AGONES_SDK_HTTP_PORT}/metadata/annotation +``` +{{% /feature %}} ### GameServer @@ -99,9 +144,16 @@ Call when you want to retrieve the backing `GameServer` configuration details - Path: `/gameserver` - Method: `GET` +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -H "Content-Type: application/json" -X GET http://localhost:59358/gameserver ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/gameserver +``` +{{% /feature %}} Response: ```json @@ -141,9 +193,16 @@ These updates will come as newline delimited JSON, send on each update. To that want to keep the http connection open, and read lines from the result stream and and process as they come in. +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -H "Content-Type: application/json" -X GET http://localhost:59358/watch/gameserver ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -H "Content-Type: application/json" -X GET http://localhost:${AGONES_SDK_HTTP_PORT}/watch/gameserver +``` +{{% /feature %}} Response: ```json @@ -162,9 +221,16 @@ Move Gameserver into a Reserved state for a certain amount of seconds for the fu #### Example +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -d '{"seconds": "5"}' -H "Content-Type: application/json" -X POST http://localhost:59358/reserve ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -d '{"seconds": "5"}' -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/reserve +``` +{{% /feature %}} ### Allocate @@ -177,6 +243,13 @@ relinquish control to an external service which likely doesn't have as much info #### Example +{{% feature expiryVersion="1.1.0" %}} ```bash $ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:59358/allocate ``` +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +```bash +$ curl -d "{}" -H "Content-Type: application/json" -X POST http://localhost:${AGONES_SDK_HTTP_PORT}/allocate +``` +{{% /feature %}} diff --git a/site/content/en/docs/Guides/Client SDKs/unreal.md b/site/content/en/docs/Guides/Client SDKs/unreal.md index e1182e206a..5da89d508c 100644 --- a/site/content/en/docs/Guides/Client SDKs/unreal.md +++ b/site/content/en/docs/Guides/Client SDKs/unreal.md @@ -31,7 +31,12 @@ At this moment we do not provide binaries for the plugin. This requires you to c The settings for the Agones Plugin can be found in the Unreal Engine editor `Edit > Project Settings > Plugins > Agones` Available settings: +{{% feature expiryVersion="1.1.0" %}} - Agones Sidecar IP. (default: `http://localhost:59358`) +{{% /feature %}} +{{% feature publishVersion="1.1.0" %}} +- Agones Sidecar IP. (default: `http://localhost:${AGONES_SDK_HTTP_PORT}`) +{{% /feature %}} - Health Ping Enabled. Whether the server sends a health ping to the Agones sidecar. (default: `true`) - Health Ping Seconds. Interval of the server sending a health ping to the Agones sidecar. (default: `5`) - Debug Logging Enabled. Debug logging for development of this Plugin. (default: `false`)