Skip to content

Commit

Permalink
Rolling updates for Fleets
Browse files Browse the repository at this point in the history
This implements a configurable rolling update strategy
for Fleets that also ensures that Allocated GameServers
are not interuppted.

Also includes updates to documentation.

Closes googleforgames#70
  • Loading branch information
markmandel committed May 18, 2018
1 parent 8ff7632 commit c87fd20
Show file tree
Hide file tree
Showing 6 changed files with 465 additions and 89 deletions.
14 changes: 10 additions & 4 deletions docs/fleet_spec.md
Expand Up @@ -18,7 +18,10 @@ metadata:
spec:
replicas: 2
strategy:
type: Recreate
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
template:
metadata:
labels:
Expand Down Expand Up @@ -51,9 +54,12 @@ The `spec` field is the actual `Fleet` specification and it is composed as follo

- `replicas` is the number of `GameServers` to keep Ready or Allocated in this Fleet
- `strategy` is the `GameServer` replacement strategy for when the `GameServer` template is edited.
`type` "Recreate" is the only option. A "RollingUpdate" option will be implemented soon.
- `Recreate` terminates all non-allocated `GameServers`, and starts up a new set with
the new `GameServer` configuration to replace them.
- `type` is replacement strategy for when the GameServer template is changed. Default option is "RollingUpdate", but "Recreate" is also available.
- `RollingUpdate` will increment by `maxSurge` value on each iteration, while decrementing by `maxUnavailable` on each iteration, until all GameServers have been switched from one version to another.
- `Recreate` terminates all non-allocated `GameServers`, and starts up a new set with the new `GameServer` configuration to replace them.
- `rollingUpdate` is only relevant when `type: RollingUpdate`
- `maxSurge` is the amount to increment the new GameServers by. Defaults to 25%
- `maxUnavailable` is the amount to decrements GameServers by. Defaults to 25%
- `template` a full `GameServer` configuration template.
See the [GameServer](./gameserver_spec.md) reference for all available fields.

Expand Down
13 changes: 10 additions & 3 deletions examples/fleet.yaml
Expand Up @@ -30,10 +30,17 @@ spec:
# a GameServer template - see:
# https://github.com/GoogleCloudPlatform/agones/blob/master/docs/gameserver_spec.md for all the options
strategy:
# The replacement strategy for when the GameServer template is changed. Current option is "Recreate",
# "RollingUpdate" will come at a later date.
# The replacement strategy for when the GameServer template is changed. Default option is "RollingUpdate",
# "RollingUpdate" will increment by maxSurge value on each iteration, while decrementing by maxUnavailable on each
# iteration, until all GameServers have been switched from one version to another.
# "Recreate" terminates all non-allocated GameServers, and starts up a new set with the new details to replace them.
type: Recreate
type: RollingUpdate
# Only relevant when `type: RollingUpdate`
rollingUpdate:
# the amount to increment the new GameServers by. Defaults to 25%
maxSurge: 25%
# the amount to decrements GameServers by. Defaults to 25%
maxUnavailable: 25%
template:
# GameServer metadata
metadata:
Expand Down
60 changes: 51 additions & 9 deletions pkg/apis/stable/v1alpha1/fleet.go
Expand Up @@ -18,6 +18,7 @@ import (
"agones.dev/agones/pkg/apis/stable"
appsv1 "k8s.io/api/apps/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)

const (
Expand Down Expand Up @@ -74,7 +75,6 @@ func (f *Fleet) GameServerSet() *GameServerSet {
gsSet := &GameServerSet{
ObjectMeta: *f.Spec.Template.ObjectMeta.DeepCopy(),
Spec: GameServerSetSpec{
Replicas: f.Spec.Replicas,
Template: f.Spec.Template,
},
}
Expand Down Expand Up @@ -102,18 +102,60 @@ func (f *Fleet) GameServerSet() *GameServerSet {
// ApplyDefaults applies default values to the Fleet
func (f *Fleet) ApplyDefaults() {
if f.Spec.Strategy.Type == "" {
f.Spec.Strategy.Type = appsv1.RecreateDeploymentStrategyType
f.Spec.Strategy.Type = appsv1.RollingUpdateDeploymentStrategyType
}

if f.Spec.Strategy.Type == appsv1.RollingUpdateDeploymentStrategyType {
if f.Spec.Strategy.RollingUpdate == nil {
f.Spec.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{}
}

def := intstr.FromString("25%")
if f.Spec.Strategy.RollingUpdate.MaxSurge == nil {
f.Spec.Strategy.RollingUpdate.MaxSurge = &def
}
if f.Spec.Strategy.RollingUpdate.MaxUnavailable == nil {
f.Spec.Strategy.RollingUpdate.MaxUnavailable = &def
}
}
}

// UpperBoundReplicas returns whichever is smaller,
// the value i, or the f.Spec.Replicas.
func (f *Fleet) UpperBoundReplicas(i int32) int32 {
if i > f.Spec.Replicas {
return f.Spec.Replicas
}
return i
}

// LowerBoundReplicas returns 0 (the minimum value for
// replicas) if i is < 0
func (f *Fleet) LowerBoundReplicas(i int32) int32 {
if i < 0 {
return 0
}
return i
}

// SumStatusAllocatedReplicas returns the total number of
// Status.AllocatedReplicas in the list of GameServerSets
func SumStatusAllocatedReplicas(list []*GameServerSet) int32 {
total := int32(0)
for _, gsSet := range list {
total += gsSet.Status.AllocatedReplicas
}

return total
}

// ReplicasMinusSumAllocated returns the number of Replicas that are
// currently configured against this fleet, subtracting the number
// of allocated GameServers counted on this list of GameServerSets
func (f *Fleet) ReplicasMinusSumAllocated(list []*GameServerSet) int32 {
result := f.Spec.Replicas
// SumStatusReplicas returns the total number of
// Status.Replicas in the list of GameServerSets
func SumStatusReplicas(list []*GameServerSet) int32 {
total := int32(0)
for _, gsSet := range list {
result -= gsSet.Status.AllocatedReplicas
total += gsSet.Status.Replicas
}

return result
return total
}
52 changes: 38 additions & 14 deletions pkg/apis/stable/v1alpha1/fleet_test.go
Expand Up @@ -50,32 +50,56 @@ func TestFleetGameServerSetGameServer(t *testing.T) {
assert.Equal(t, f.ObjectMeta.Namespace, gsSet.ObjectMeta.Namespace)
assert.Equal(t, f.ObjectMeta.Name+"-", gsSet.ObjectMeta.GenerateName)
assert.Equal(t, f.ObjectMeta.Name, gsSet.ObjectMeta.Labels[FleetGameServerSetLabel])
assert.Equal(t, f.Spec.Replicas, gsSet.Spec.Replicas)
assert.Equal(t, int32(0), gsSet.Spec.Replicas)
assert.Equal(t, f.Spec.Template, gsSet.Spec.Template)
assert.True(t, v1.IsControlledBy(gsSet, &f))
}

func TestFleetReplicasMinusSumAllocated(t *testing.T) {
f := Fleet{
Spec: FleetSpec{Replicas: 10},
}
func TestFleetApplyDefaults(t *testing.T) {
f := &Fleet{}

// gate
assert.EqualValues(t, "", f.Spec.Strategy.Type)

f.ApplyDefaults()
assert.Equal(t, appsv1.RollingUpdateDeploymentStrategyType, f.Spec.Strategy.Type)
assert.Equal(t, "25%", f.Spec.Strategy.RollingUpdate.MaxUnavailable.String())
assert.Equal(t, "25%", f.Spec.Strategy.RollingUpdate.MaxSurge.String())
}

func TestFleetUpperBoundReplicas(t *testing.T) {
f := &Fleet{Spec: FleetSpec{Replicas: 10}}

assert.Equal(t, int32(10), f.ReplicasMinusSumAllocated(nil))
assert.Equal(t, int32(10), f.UpperBoundReplicas(12))
assert.Equal(t, int32(10), f.UpperBoundReplicas(10))
assert.Equal(t, int32(5), f.UpperBoundReplicas(5))
}

func TestFleetLowerBoundReplicas(t *testing.T) {
f := &Fleet{Spec: FleetSpec{Replicas: 10}}

assert.Equal(t, int32(5), f.LowerBoundReplicas(5))
assert.Equal(t, int32(0), f.LowerBoundReplicas(0))
assert.Equal(t, int32(0), f.LowerBoundReplicas(-5))
}

func TestSumStatusAllocatedReplicas(t *testing.T) {
f := Fleet{}
gsSet1 := f.GameServerSet()
gsSet1.Status.AllocatedReplicas = 2

gsSet2 := f.GameServerSet()
gsSet2.Status.AllocatedReplicas = 3

assert.Equal(t, int32(5), f.ReplicasMinusSumAllocated([]*GameServerSet{gsSet1, gsSet2}))
assert.Equal(t, int32(5), SumStatusAllocatedReplicas([]*GameServerSet{gsSet1, gsSet2}))
}

func TestFleetApplyDefaults(t *testing.T) {
f := &Fleet{}

// gate
assert.EqualValues(t, "", f.Spec.Strategy.Type)
func TestSumStatusReplicas(t *testing.T) {
fixture := []*GameServerSet{
{Status: GameServerSetStatus{Replicas: 10}},
{Status: GameServerSetStatus{Replicas: 15}},
{Status: GameServerSetStatus{Replicas: 5}},
}

f.ApplyDefaults()
assert.Equal(t, appsv1.RecreateDeploymentStrategyType, f.Spec.Strategy.Type)
assert.Equal(t, int32(30), SumStatusReplicas(fixture))
}

0 comments on commit c87fd20

Please sign in to comment.