Skip to content

Commit 6a07fb1

Browse files
author
Andrew Jenkins
committed
Add Traffic Mirroring for Istio Service Mesh
Traffic mirroring is a pre-stage for canary deployments. When mirroring is enabled, at the beginning of a canary deployment traffic is mirrored to the canary instead of shifted for one canary period. The service mesh should mirror by copying the request and sending one copy to the primary and one copy to the canary; only the response from the primary is sent to the user. The response from the canary is only used for collecting metrics. Once the mirror period is over, the canary proceeds as usual, shifting traffic from primary to canary until complete. Added TestScheduler_Mirroring unit test.
1 parent 37fb832 commit 6a07fb1

4 files changed

Lines changed: 96 additions & 9 deletions

File tree

pkg/apis/flagger/v1alpha3/types.go

100755100644
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ type CanaryAnalysis struct {
111111
Interval string `json:"interval"`
112112
Threshold int `json:"threshold"`
113113
MaxWeight int `json:"maxWeight"`
114+
Mirror bool `json:"mirror,omitempty"`
114115
StepWeight int `json:"stepWeight"`
115116
Metrics []CanaryMetric `json:"metrics"`
116117
Webhooks []CanaryWebhook `json:"webhooks,omitempty"`

pkg/controller/controller_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,12 @@ func newTestCanary() *v1alpha3.Canary {
267267
return cd
268268
}
269269

270+
func newTestCanaryMirror() *v1alpha3.Canary {
271+
cd := newTestCanary()
272+
cd.Spec.CanaryAnalysis.Mirror = true
273+
return cd
274+
}
275+
270276
func newTestCanaryAB() *v1alpha3.Canary {
271277
cd := &v1alpha3.Canary{
272278
TypeMeta: metav1.TypeMeta{APIVersion: v1alpha3.SchemeGroupVersion.String()},

pkg/controller/scheduler.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,9 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
282282
}
283283

284284
// check if the canary success rate is above the threshold
285-
// skip check if no traffic is routed to canary
286-
if canaryWeight == 0 && cd.Status.Iterations == 0 {
285+
// skip check if no traffic is routed or mirrored to canary
286+
if canaryWeight == 0 && cd.Status.Iterations == 0 &&
287+
(cd.Spec.CanaryAnalysis.Mirror == false || mirrored == false) {
287288
c.recordEventInfof(cd, "Starting canary analysis for %s.%s", cd.Spec.TargetRef.Name, cd.Namespace)
288289

289290
// run pre-rollout web hooks
@@ -446,13 +447,34 @@ func (c *Controller) advanceCanary(name string, namespace string, skipLivenessCh
446447

447448
// canary incremental traffic weight
448449
if canaryWeight < maxWeight {
449-
primaryWeight -= cd.Spec.CanaryAnalysis.StepWeight
450-
if primaryWeight < 0 {
451-
primaryWeight = 0
452-
}
453-
canaryWeight += cd.Spec.CanaryAnalysis.StepWeight
454-
if primaryWeight > 100 {
455-
primaryWeight = 100
450+
// If in "mirror" mode, do one step of mirroring before shifting traffic to canary.
451+
// When mirroring, all requests go to primary and canary, but only responses from
452+
// primary go back to the user.
453+
c.logger.With("canary", fmt.Sprintf("%s.%s", name, namespace)).
454+
Warnf("Deciding on canary, input: %d/%d/%t, Mirror: %t",
455+
primaryWeight, canaryWeight, mirrored, cd.Spec.CanaryAnalysis.Mirror)
456+
if cd.Spec.CanaryAnalysis.Mirror && canaryWeight == 0 {
457+
if mirrored == false {
458+
mirrored = true
459+
primaryWeight = 100
460+
canaryWeight = 0
461+
} else {
462+
mirrored = false
463+
primaryWeight = 100 - cd.Spec.CanaryAnalysis.StepWeight
464+
canaryWeight = cd.Spec.CanaryAnalysis.StepWeight
465+
}
466+
c.logger.With("canary", fmt.Sprintf("%s.%s", name, namespace)).
467+
Warnf("Running mirror step %d/%d/%t", primaryWeight, canaryWeight, mirrored)
468+
} else {
469+
primaryWeight -= cd.Spec.CanaryAnalysis.StepWeight
470+
if primaryWeight < 0 {
471+
primaryWeight = 0
472+
}
473+
canaryWeight += cd.Spec.CanaryAnalysis.StepWeight
474+
// FIXME(andrewj): Is this a bug? Should floor(primaryWeight, 100)?
475+
if primaryWeight > 100 {
476+
primaryWeight = 100
477+
}
456478
}
457479

458480
// check promotion gate

pkg/controller/scheduler_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,64 @@ func TestScheduler_Promotion(t *testing.T) {
281281
}
282282
}
283283

284+
func TestScheduler_Mirroring(t *testing.T) {
285+
mocks := SetupMocks(newTestCanaryMirror())
286+
// init
287+
mocks.ctrl.advanceCanary("podinfo", "default", true)
288+
289+
// update
290+
dep2 := newTestDeploymentV2()
291+
_, err := mocks.kubeClient.AppsV1().Deployments("default").Update(dep2)
292+
if err != nil {
293+
t.Fatal(err.Error())
294+
}
295+
296+
// detect pod spec changes
297+
mocks.ctrl.advanceCanary("podinfo", "default", true)
298+
299+
// advance
300+
mocks.ctrl.advanceCanary("podinfo", "default", true)
301+
302+
// check if traffic is mirrored to canary
303+
primaryWeight, canaryWeight, mirrored, err := mocks.router.GetRoutes(mocks.canary)
304+
if err != nil {
305+
t.Fatal(err.Error())
306+
}
307+
308+
if primaryWeight != 100 {
309+
t.Errorf("Got primary route %v wanted %v", primaryWeight, 100)
310+
}
311+
312+
if canaryWeight != 0 {
313+
t.Errorf("Got canary route %v wanted %v", canaryWeight, 0)
314+
}
315+
316+
if mirrored != true {
317+
t.Errorf("Got mirrored %v wanted %v", mirrored, true)
318+
}
319+
320+
// advance
321+
mocks.ctrl.advanceCanary("podinfo", "default", true)
322+
323+
// check if traffic is mirrored to canary
324+
primaryWeight, canaryWeight, mirrored, err = mocks.router.GetRoutes(mocks.canary)
325+
if err != nil {
326+
t.Fatal(err.Error())
327+
}
328+
329+
if primaryWeight != 90 {
330+
t.Errorf("Got primary route %v wanted %v", primaryWeight, 90)
331+
}
332+
333+
if canaryWeight != 10 {
334+
t.Errorf("Got canary route %v wanted %v", canaryWeight, 10)
335+
}
336+
337+
if mirrored != false {
338+
t.Errorf("Got mirrored %v wanted %v", mirrored, false)
339+
}
340+
}
341+
284342
func TestScheduler_ABTesting(t *testing.T) {
285343
mocks := SetupMocks(newTestCanaryAB())
286344
// init

0 commit comments

Comments
 (0)