Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions pkg/gameservers/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,13 @@ const (
sdkserverSidecarName = "agones-gameserver-sidecar"
grpcPortEnvVar = "AGONES_SDK_GRPC_PORT"
httpPortEnvVar = "AGONES_SDK_HTTP_PORT"
goMaxProcsEnvVar = "GOMAXPROCS"
goMemLimitEnvVar = "GOMEMLIMIT"
passthroughPortEnvVar = "PASSTHROUGH"

sidecarGoMaxProcs = "1"
sidecarGoMemLimitRequestPercentage = 90
Comment on lines +71 to +72
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seem like good defaults, but I wonder if this should be configurable, just in case. 🤔 WDYT/

bytesPerMebibyte = 1024 * 1024
)

// Extensions struct contains what is needed to bind webhook handlers
Expand Down Expand Up @@ -763,6 +769,10 @@ func (c *Controller) sidecar(gs *agonesv1.GameServer) corev1.Container {
Name: "REQUESTS_RATE_LIMIT",
Value: c.sidecarRequestsRateLimit.String(),
},
{
Name: goMaxProcsEnvVar,
Value: sidecarGoMaxProcs,
},
},
Resources: corev1.ResourceRequirements{},
LivenessProbe: &corev1.Probe{
Expand All @@ -777,6 +787,18 @@ func (c *Controller) sidecar(gs *agonesv1.GameServer) corev1.Container {
},
}

if !c.sidecarMemoryRequest.IsZero() {
goMemLimitBytes := c.sidecarMemoryRequest.Value() * sidecarGoMemLimitRequestPercentage / 100
goMemLimitMiB := goMemLimitBytes / bytesPerMebibyte
if goMemLimitMiB < 1 {
goMemLimitMiB = 1
}
sidecar.Env = append(sidecar.Env, corev1.EnvVar{
Name: goMemLimitEnvVar,
Value: fmt.Sprintf("%dMiB", goMemLimitMiB),
})
}

if gs.Spec.SdkServer.GRPCPort != 0 {
sidecar.Args = append(sidecar.Args, fmt.Sprintf("--grpc-port=%d", gs.Spec.SdkServer.GRPCPort))
}
Expand Down
78 changes: 77 additions & 1 deletion pkg/gameservers/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1621,14 +1621,19 @@ func TestControllerCreateGameServerPod(t *testing.T) {
assert.Equal(t, sidecarContainer.Resources.Requests.Cpu(), &c.sidecarCPURequest)
assert.Equal(t, sidecarContainer.Resources.Limits.Memory(), &c.sidecarMemoryLimit)
assert.Equal(t, sidecarContainer.Resources.Requests.Memory(), &c.sidecarMemoryRequest)
assert.Len(t, sidecarContainer.Env, 5, "5 env vars")
assert.Len(t, sidecarContainer.Env, 7, "7 env vars")
assert.Equal(t, "GAMESERVER_NAME", sidecarContainer.Env[0].Name)
assert.Equal(t, fixture.ObjectMeta.Name, sidecarContainer.Env[0].Value)
assert.Equal(t, "POD_NAMESPACE", sidecarContainer.Env[1].Name)
assert.Equal(t, "FEATURE_GATES", sidecarContainer.Env[2].Name)
assert.Equal(t, "LOG_LEVEL", sidecarContainer.Env[3].Name)
assert.Equal(t, "REQUESTS_RATE_LIMIT", sidecarContainer.Env[4].Name)
assert.Equal(t, goMaxProcsEnvVar, sidecarContainer.Env[5].Name)
assert.Equal(t, goMemLimitEnvVar, sidecarContainer.Env[6].Name)
assert.Equal(t, "500ms", sidecarContainer.Env[4].Value)
assert.Equal(t, sidecarGoMaxProcs, sidecarContainer.Env[5].Value)
goMemLimitBytes := c.sidecarMemoryRequest.Value() * sidecarGoMemLimitRequestPercentage / 100
assert.Equal(t, strconv.FormatInt(goMemLimitBytes, 10), sidecarContainer.Env[6].Value)
assert.Equal(t, string(fixture.Spec.SdkServer.LogLevel), sidecarContainer.Env[3].Value)
assert.Equal(t, *sidecarContainer.SecurityContext.AllowPrivilegeEscalation, false)
assert.Equal(t, *sidecarContainer.SecurityContext.RunAsNonRoot, true)
Expand Down Expand Up @@ -2527,6 +2532,77 @@ func TestControllerAddSDKServerEnvVars(t *testing.T) {
})
}

func TestControllerSidecarGoRuntimeResourceHints(t *testing.T) {
t.Parallel()

fixtures := map[string]struct {
memoryRequest resource.Quantity
expectedEnv map[string]string
}{
"with memory request": {
memoryRequest: resource.MustParse("100Mi"),
expectedEnv: map[string]string{
goMaxProcsEnvVar: sidecarGoMaxProcs,
goMemLimitEnvVar: "90MiB",
},
},
"with GiB memory request": {
memoryRequest: resource.MustParse("1Gi"),
expectedEnv: map[string]string{
goMaxProcsEnvVar: sidecarGoMaxProcs,
goMemLimitEnvVar: "921MiB",
},
},
"with non-whole MiB memory request": {
memoryRequest: resource.MustParse("100500Ki"),
expectedEnv: map[string]string{
goMaxProcsEnvVar: sidecarGoMaxProcs,
goMemLimitEnvVar: "88MiB",
},
},
"with small memory request": {
memoryRequest: resource.MustParse("1"),
expectedEnv: map[string]string{
goMaxProcsEnvVar: sidecarGoMaxProcs,
goMemLimitEnvVar: "1MiB",
},
},
"without memory request": {
memoryRequest: resource.Quantity{},
expectedEnv: map[string]string{
goMaxProcsEnvVar: sidecarGoMaxProcs,
},
},
}

for name, tc := range fixtures {
t.Run(name, func(t *testing.T) {
t.Parallel()

c, _ := newFakeController()
c.sidecarMemoryRequest = tc.memoryRequest
gs := &agonesv1.GameServer{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: newSingleContainerSpec(),
}
gs.ApplyDefaults()

sidecar := c.sidecar(gs)
env := map[string]string{}
for _, e := range sidecar.Env {
env[e.Name] = e.Value
}

for name, value := range tc.expectedEnv {
assert.Equal(t, value, env[name])
}
if _, ok := tc.expectedEnv[goMemLimitEnvVar]; !ok {
assert.NotContains(t, env, goMemLimitEnvVar)
}
})
}
}

// testNoChange runs a test with a state that doesn't exist, to ensure a handler
// doesn't do process anything beyond the state it is meant to handle.
func testNoChange(t *testing.T, state agonesv1.GameServerState, f func(*Controller, *agonesv1.GameServer) (*agonesv1.GameServer, error)) {
Expand Down
3 changes: 3 additions & 0 deletions site/content/en/docs/Advanced/limiting-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,6 @@ You can do this through the [Helm configuration]({{< ref "/docs/Installation/Ins
By default, this is set to having a CPU request value of 30m, with no hard CPU limit. This ensures that the sidecar always has enough CPU
to function, but it is configurable in case a lower, or higher value is required on your clusters, or if you desire
hard limit.

When a memory request is configured, Agones sets the SDK sidecar's `GOMEMLIMIT` to 90% of that request so the Go runtime
keeps its managed memory close to the amount scheduled for the sidecar.
2 changes: 1 addition & 1 deletion site/content/en/docs/Installation/Install Agones/helm.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ The following tables lists the configurable parameters of the Agones chart and t
| `agones.image.sdk.tag` | Image tag for the sdk | value of `agones.image.tag` |
| `agones.image.sdk.cpuRequest` | The [cpu request][cpu-constraints] for sdk server container | `30m` |
| `agones.image.sdk.cpuLimit` | The [cpu limit][cpu-constraints] for the sdk server container | `0` (none) |
| `agones.image.sdk.memoryRequest` | The [memory request][memory-constraints] for sdk server container | `0` (none) |
| `agones.image.sdk.memoryRequest` | The [memory request][memory-constraints] for sdk server container. When set, 90% is used as the SDK sidecar `GOMEMLIMIT` | `0` (none) |
| `agones.image.sdk.memoryLimit` | The [memory limit][memory-constraints] for the sdk server container | `0` (none) |
| `agones.image.sdk.alwaysPull` | Tells if the sdk image should always be pulled | `false` |
| `agones.image.ping.name` | Image name for the ping service | `agones-ping` |
Expand Down
Loading