Skip to content

Commit

Permalink
GameServer Pod: Stable Network ID
Browse files Browse the repository at this point in the history
This change sets the hostName (if not already set) on the Pod of the
GameServer, if an end user would like a DNS entry to communicate
directly to the GameServer Pod in the cluster.

Since this is a relatively minor change, no FeatureFlag was created.

Closes googleforgames#2704
  • Loading branch information
markmandel committed Dec 1, 2022
1 parent 85726ea commit 6f2ae75
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 0 deletions.
6 changes: 6 additions & 0 deletions pkg/apis/agones/v1/gameserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"encoding/json"
"fmt"
"net"
"strings"

"agones.dev/agones/pkg"
"agones.dev/agones/pkg/apis"
Expand Down Expand Up @@ -596,6 +597,11 @@ func (gs *GameServer) Pod(sidecars ...corev1.Container) (*corev1.Pod, error) {
Spec: *gs.Spec.Template.Spec.DeepCopy(),
}

if len(pod.Spec.Hostname) == 0 {
// replace . with - since it must match RFC 1123
pod.Spec.Hostname = strings.ReplaceAll(gs.ObjectMeta.Name, ".", "-")
}

gs.podObjectMeta(pod)
for _, p := range gs.Spec.Ports {
cp := corev1.ContainerPort{
Expand Down
21 changes: 21 additions & 0 deletions pkg/apis/agones/v1/gameserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"agones.dev/agones/pkg/apis/agones"
"agones.dev/agones/pkg/util/runtime"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -1154,6 +1155,7 @@ func TestGameServerPodNoErrors(t *testing.T) {
pod, err := fixture.Pod()
assert.Nil(t, err, "Pod should not return an error")
assert.Equal(t, fixture.ObjectMeta.Name, pod.ObjectMeta.Name)
assert.Equal(t, fixture.ObjectMeta.Name, pod.Spec.Hostname)
assert.Equal(t, fixture.ObjectMeta.Namespace, pod.ObjectMeta.Namespace)
assert.Equal(t, "gameserver", pod.ObjectMeta.Labels[agones.GroupName+"/role"])
assert.Equal(t, fixture.ObjectMeta.Name, pod.ObjectMeta.Labels[GameServerPodLabel])
Expand All @@ -1165,6 +1167,25 @@ func TestGameServerPodNoErrors(t *testing.T) {
assert.True(t, metav1.IsControlledBy(pod, fixture))
}

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

fixture := defaultGameServer()
fixture.ObjectMeta.Name = "test-1.0"
fixture.ApplyDefaults()
pod, err := fixture.Pod()
require.NoError(t, err)
assert.Equal(t, "test-1-0", pod.Spec.Hostname)

fixture = defaultGameServer()
fixture.ApplyDefaults()
expected := "ORANGE"
fixture.Spec.Template.Spec.Hostname = expected
pod, err = fixture.Pod()
require.NoError(t, err)
assert.Equal(t, expected, pod.Spec.Hostname)
}

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

Expand Down
17 changes: 17 additions & 0 deletions site/content/en/docs/Reference/gameserver.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,23 @@ The `spec` field is the actual GameServer specification and it is composed as fo
The GameServer resource does not support updates. If you need to make regular updates to the GameServer spec, consider using a [Fleet]({{< ref "/docs/Reference/fleet.md" >}}).
{{< /alert >}}

## Stable Network ID

Each Pod attached to a `GameServer` derives its hostname from the name of the `GameServer`.
A group of `Pods` attached to `GameServers` can use a
[Headless Service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services) to control
the domain of the Pods, along with providing
a [`subdomain` value to the `GameServer` `PodTemplateSpec`](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-hostname-and-subdomain-fields)
to provide all the required details such that Kubernetes will create a DNS record for each Pod behind the Service.

You are also responsible for setting the labels on the `GameServer.Spec.Template.Metadata` to set the labels on the
created Pods and creating the Headless Service responsible for the network identity of the pods, Agones will not do
this for you, as a stable DNS record is not required for all use cases.

To ensure that the `hostName` value matches
[RFC 1123](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-label-names), any `.` values
in the `GameServer` name are replaced by `-` when setting the `hostName` value.

## GameServer State Diagram

The following diagram shows the lifecycle of a `GameServer`.
Expand Down
49 changes: 49 additions & 0 deletions test/e2e/gameserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,55 @@ func TestCreateConnect(t *testing.T) {
assert.Equal(t, "ACK: Hello World !\n", reply)
}

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

pods := framework.KubeClient.CoreV1().Pods(framework.Namespace)

fixtures := map[string]struct {
setup func(gs *agonesv1.GameServer)
test func(gs *agonesv1.GameServer, pod *corev1.Pod)
}{
"standard hostname": {
setup: func(_ *agonesv1.GameServer) {},
test: func(gs *agonesv1.GameServer, pod *corev1.Pod) {
assert.Equal(t, gs.ObjectMeta.Name, pod.Spec.Hostname)
},
},
"a . in the name": {
setup: func(gs *agonesv1.GameServer) {
gs.ObjectMeta.GenerateName = "game-server-1.0-"
},
test: func(gs *agonesv1.GameServer, pod *corev1.Pod) {
assert.Equal(t, strings.ReplaceAll(gs.ObjectMeta.Name, ".", "-"), pod.Spec.Hostname)
},
},
// generated name will automatically truncate to 63 chars.
"generated with > 63 chars": {
setup: func(gs *agonesv1.GameServer) {
gs.ObjectMeta.GenerateName = "game-server-" + strings.Repeat("n", 100)
},
test: func(gs *agonesv1.GameServer, pod *corev1.Pod) {
assert.Equal(t, gs.ObjectMeta.Name, pod.Spec.Hostname)
},
},
// Note: no need to test for a gs.ObjectMeta.Name > 63 chars, as it will be rejected as invalid
}

for k, v := range fixtures {
t.Run(k, func(t *testing.T) {
gs := framework.DefaultGameServer(framework.Namespace)
gs.Spec.Template.Spec.Subdomain = "default"
v.setup(gs)
readyGs, err := framework.CreateGameServerAndWaitUntilReady(t, framework.Namespace, gs)
require.NoError(t, err)
pod, err := pods.Get(context.Background(), readyGs.ObjectMeta.Name, metav1.GetOptions{})
require.NoError(t, err)
v.test(readyGs, pod)
})
}
}

// nolint:dupl
func TestSDKSetLabel(t *testing.T) {
t.Parallel()
Expand Down

0 comments on commit 6f2ae75

Please sign in to comment.