Skip to content

Commit f709429

Browse files
committed
experiment: add rolling-update scenario
1 parent 7d287b6 commit f709429

File tree

5 files changed

+175
-5
lines changed

5 files changed

+175
-5
lines changed

docs/evaluation.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,10 @@ Usage:
7575
experiment [command]
7676
7777
Available Scenarios
78-
Available Scenarios
79-
basic Basic load test, create 9k websites in 15 minutes
80-
chaos Create 4.5k websites over 15 minutes and terminate a random shard every 5 minutes
81-
scale-out Measure scale-out properties with a high churn rate
78+
basic Basic load test, create 9k websites in 15 minutes
79+
chaos Create 4.5k websites over 15 minutes and terminate a random shard every 5 minutes
80+
rolling-update Create 9k websites in 15 minutes while rolling the operator
81+
scale-out Measure scale-out properties with a high churn rate
8282
...
8383
```
8484

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
4+
resources:
5+
- ../base
6+
7+
patches:
8+
- target:
9+
kind: Job
10+
name: experiment
11+
patch: |
12+
- op: add
13+
path: /spec/template/spec/containers/0/args/-
14+
value: rolling-update

webhosting-operator/pkg/experiment/generator/utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func StopOnContextCanceled(r reconcile.Reconciler) reconcile.Reconciler {
6565
return reconcile.Func(func(ctx context.Context, request reconcile.Request) (reconcile.Result, error) {
6666
result, err := r.Reconcile(ctx, request)
6767
if errors.Is(err, context.Canceled) {
68-
err = nil
68+
return reconcile.Result{}, nil
6969
}
7070
return result, err
7171
})

webhosting-operator/pkg/experiment/scenario/all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ package all
2020
import (
2121
_ "github.com/timebertt/kubernetes-controller-sharding/webhosting-operator/pkg/experiment/scenario/basic"
2222
_ "github.com/timebertt/kubernetes-controller-sharding/webhosting-operator/pkg/experiment/scenario/chaos"
23+
_ "github.com/timebertt/kubernetes-controller-sharding/webhosting-operator/pkg/experiment/scenario/rolling-update"
2324
_ "github.com/timebertt/kubernetes-controller-sharding/webhosting-operator/pkg/experiment/scenario/scale-out"
2425
)
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/*
2+
Copyright 2025 Tim Ebert.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package rolling_update
18+
19+
import (
20+
"context"
21+
"fmt"
22+
"time"
23+
24+
"golang.org/x/time/rate"
25+
appsv1 "k8s.io/api/apps/v1"
26+
apierrors "k8s.io/apimachinery/pkg/api/errors"
27+
"k8s.io/apimachinery/pkg/types"
28+
"sigs.k8s.io/controller-runtime/pkg/client"
29+
logf "sigs.k8s.io/controller-runtime/pkg/log"
30+
31+
webhostingv1alpha1 "github.com/timebertt/kubernetes-controller-sharding/webhosting-operator/pkg/apis/webhosting/v1alpha1"
32+
"github.com/timebertt/kubernetes-controller-sharding/webhosting-operator/pkg/experiment"
33+
"github.com/timebertt/kubernetes-controller-sharding/webhosting-operator/pkg/experiment/generator"
34+
"github.com/timebertt/kubernetes-controller-sharding/webhosting-operator/pkg/experiment/scenario/base"
35+
)
36+
37+
const ScenarioName = "rolling-update"
38+
39+
func init() {
40+
s := &scenario{}
41+
s.Scenario = &base.Scenario{
42+
ScenarioName: ScenarioName,
43+
Delegate: s,
44+
}
45+
46+
experiment.RegisterScenario(s)
47+
}
48+
49+
type scenario struct {
50+
*base.Scenario
51+
}
52+
53+
func (s *scenario) Description() string {
54+
return "Create 9k websites in 15 minutes while rolling the operator"
55+
}
56+
57+
func (s *scenario) LongDescription() string {
58+
return `The ` + ScenarioName + ` scenario combines several operations typical for a lively operator environment with rolling updates:
59+
- website creation: 10800 over 15m
60+
- website deletion: 1800 over 15m
61+
- website spec changes: 1/m per object, max 150/s
62+
- rolling update of webhosting-operator: 1 every 5m
63+
`
64+
}
65+
66+
func (s *scenario) Prepare(ctx context.Context) error {
67+
s.Log.Info("Preparing themes")
68+
if err := generator.CreateThemes(ctx, s.Client, 50, generator.WithLabels(s.Labels), generator.WithOwnerReference(s.OwnerRef)); err != nil {
69+
return err
70+
}
71+
72+
s.Log.Info("Preparing projects")
73+
if err := generator.CreateProjects(ctx, s.Client, 20, generator.WithLabels(s.Labels), generator.WithOwnerReference(s.OwnerRef)); err != nil {
74+
return err
75+
}
76+
77+
return nil
78+
}
79+
80+
func (s *scenario) Run(ctx context.Context) error {
81+
// website-generator: creates about 10800 websites over 15 minutes
82+
// website-deleter: deletes about 1800 websites over 15 minutes
83+
// => in total, there will be about 9000 websites after 15 minutes
84+
if err := (&generator.Every{
85+
Name: "website-generator",
86+
Do: func(ctx context.Context, c client.Client) error {
87+
return generator.CreateWebsite(ctx, c, generator.WithLabels(s.Labels))
88+
},
89+
Rate: rate.Limit(12),
90+
}).AddToManager(s.Manager); err != nil {
91+
return fmt.Errorf("error adding website-generator: %w", err)
92+
}
93+
94+
if err := (&generator.Every{
95+
Name: "website-deleter",
96+
Do: func(ctx context.Context, c client.Client) error {
97+
return generator.DeleteWebsite(ctx, c, s.Labels)
98+
},
99+
Rate: rate.Limit(2),
100+
}).AddToManager(s.Manager); err != nil {
101+
return fmt.Errorf("error adding website-deleter: %w", err)
102+
}
103+
104+
// trigger individual spec changes for website once per minute
105+
// => peaks at about 150 spec changes per second at the end of the experiment
106+
// (triggers roughly double the reconciliation rate in website controller because of deployment watches)
107+
if err := (&generator.ForEach[*webhostingv1alpha1.Website]{
108+
Name: "website-mutator",
109+
Do: func(ctx context.Context, c client.Client, obj *webhostingv1alpha1.Website) error {
110+
return client.IgnoreNotFound(generator.MutateWebsite(ctx, c, obj, s.Labels))
111+
},
112+
Every: time.Minute,
113+
}).AddToManager(s.Manager); err != nil {
114+
return fmt.Errorf("error adding website-mutator: %w", err)
115+
}
116+
117+
// trigger a rolling update of the webhosting-operator every 5 minutes
118+
if err := (&generator.Every{
119+
Name: "rolling-updater",
120+
Do: triggerRollingUpdate,
121+
Rate: rate.Every(5 * time.Minute),
122+
}).AddToManager(s.Manager); err != nil {
123+
return fmt.Errorf("error adding rolling-updater: %w", err)
124+
}
125+
126+
return s.Wait(ctx, 15*time.Minute)
127+
}
128+
129+
func triggerRollingUpdate(ctx context.Context, c client.Client) error {
130+
key := client.ObjectKey{Namespace: webhostingv1alpha1.NamespaceSystem, Name: webhostingv1alpha1.WebhostingOperatorName}
131+
132+
var object client.Object = &appsv1.Deployment{}
133+
if err := c.Get(ctx, key, object); err != nil {
134+
if !apierrors.IsNotFound(err) {
135+
return err
136+
}
137+
138+
object = &appsv1.StatefulSet{}
139+
if err := c.Get(ctx, key, object); err != nil {
140+
if !apierrors.IsNotFound(err) {
141+
return err
142+
}
143+
144+
panic("neither Deployment nor StatefulSet found for webhosting-operator, aborting experiment")
145+
}
146+
}
147+
148+
patch := client.RawPatch(types.MergePatchType, []byte(`{"spec":{"template":{"metadata":{"annotations":{"rolling-update":"`+time.Now().UTC().Format(time.RFC3339)+`"}}}}}`))
149+
if err := c.Patch(ctx, object, patch); err != nil {
150+
return err
151+
}
152+
153+
logf.FromContext(ctx).Info("Triggered rolling update")
154+
return nil
155+
}

0 commit comments

Comments
 (0)