From 4cd5ad088a3da2dbb45e2e363db405c86e4c13a1 Mon Sep 17 00:00:00 2001 From: d3adb5 Date: Fri, 7 Feb 2025 10:14:43 -0800 Subject: [PATCH] feat: support additional services for blue-green Add support for additional preview and active services for the BlueGreen strategy. Signed-off-by: d3adb5 --- manifests/crds/rollout-crd.yaml | 8 +++++ manifests/install.yaml | 8 +++++ pkg/apiclient/rollout/rollout.swagger.json | 14 ++++++++ pkg/apis/rollouts/v1alpha1/types.go | 12 +++++++ rollout/bluegreen.go | 18 ++++++++++ rollout/bluegreen_test.go | 41 ++++++++++++++++++++++ rollout/service.go | 26 ++++++++++++++ ui/src/models/rollout/generated/api.ts | 12 +++++++ 8 files changed, 139 insertions(+) diff --git a/manifests/crds/rollout-crd.yaml b/manifests/crds/rollout-crd.yaml index 060158cd1b..e078ee7bd1 100755 --- a/manifests/crds/rollout-crd.yaml +++ b/manifests/crds/rollout-crd.yaml @@ -132,6 +132,14 @@ spec: type: object activeService: type: string + additionalActiveServices: + items: + type: string + type: array + additionalPreviewServices: + items: + type: string + type: array antiAffinity: properties: preferredDuringSchedulingIgnoredDuringExecution: diff --git a/manifests/install.yaml b/manifests/install.yaml index 3680da58bc..724a4d2ff8 100755 --- a/manifests/install.yaml +++ b/manifests/install.yaml @@ -12694,6 +12694,14 @@ spec: type: object activeService: type: string + additionalActiveServices: + items: + type: string + type: array + additionalPreviewServices: + items: + type: string + type: array antiAffinity: properties: preferredDuringSchedulingIgnoredDuringExecution: diff --git a/pkg/apiclient/rollout/rollout.swagger.json b/pkg/apiclient/rollout/rollout.swagger.json index 550952c111..9d5f7b2201 100755 --- a/pkg/apiclient/rollout/rollout.swagger.json +++ b/pkg/apiclient/rollout/rollout.swagger.json @@ -952,6 +952,20 @@ "type": "integer", "format": "int32", "title": "AbortScaleDownDelaySeconds adds a delay in second before scaling down the preview replicaset\nif update is aborted. 0 means not to scale down.\nDefault is 30 second\n+optional" + }, + "additionalActiveServices": { + "type": "array", + "items": { + "type": "string" + }, + "title": "AdditionalActiveServices is a list of additional Services that the\nrollout modifies during reconciliation. These will always be updated\nafter the primary active Service.\n+optional" + }, + "additionalPreviewServices": { + "type": "array", + "items": { + "type": "string" + }, + "title": "AdditionalPreviewServices is a list of additional Services that the\nrollout modifies during reconciliation. These will always be updated\nafter the primary one.\n+optional" } }, "title": "BlueGreenStrategy defines parameters for Blue Green deployment" diff --git a/pkg/apis/rollouts/v1alpha1/types.go b/pkg/apis/rollouts/v1alpha1/types.go index ea25c61349..4d5d55b684 100755 --- a/pkg/apis/rollouts/v1alpha1/types.go +++ b/pkg/apis/rollouts/v1alpha1/types.go @@ -221,6 +221,18 @@ type BlueGreenStrategy struct { // Default is 30 second // +optional AbortScaleDownDelaySeconds *int32 `json:"abortScaleDownDelaySeconds,omitempty" protobuf:"varint,14,opt,name=abortScaleDownDelaySeconds"` + + // AdditionalActiveServices is a list of additional Services that the + // rollout modifies during reconciliation. These will always be updated + // after the primary active Service. + // +optional + AdditionalActiveServices []string `json:"additionalActiveServices,omitempty" protobuf:"bytes,15,opt,name=additionalActiveServices"` + + // AdditionalPreviewServices is a list of additional Services that the + // rollout modifies during reconciliation. These will always be updated + // after the primary one. + // +optional + AdditionalPreviewServices []string `json:"additionalPreviewServices,omitempty" protobuf:"bytes,16,opt,name=additionalPreviewServices"` } // AntiAffinity defines which inter-pod scheduling rule to use for anti-affinity injection diff --git a/rollout/bluegreen.go b/rollout/bluegreen.go index cf6cbc6ac7..c0831d3e6b 100644 --- a/rollout/bluegreen.go +++ b/rollout/bluegreen.go @@ -21,6 +21,10 @@ func (c *rolloutContext) rolloutBlueGreen() error { if err != nil { return err } + additionalPreviewSvcs, additionalActiveSvcs, err := c.getAdditionalPreviewAndActiveServices() + if err != nil { + return err + } c.newRS, err = c.getAllReplicaSetsAndSyncRevision() if err != nil { return fmt.Errorf("failed to getAllReplicaSetsAndSyncRevision in rolloutBlueGreen create true: %w", err) @@ -32,6 +36,13 @@ func (c *rolloutContext) rolloutBlueGreen() error { return err } + for _, svc := range additionalPreviewSvcs { + err = c.reconcilePreviewService(svc) + if err != nil { + return err + } + } + if replicasetutil.CheckPodSpecChange(c.rollout, c.newRS) { return c.syncRolloutStatusBlueGreen(previewSvc, activeSvc) } @@ -53,6 +64,13 @@ func (c *rolloutContext) rolloutBlueGreen() error { return err } + for _, svc := range additionalActiveSvcs { + err = c.reconcileActiveService(svc) + if err != nil { + return err + } + } + err = c.awsVerifyTargetGroups(activeSvc) if err != nil { return err diff --git a/rollout/bluegreen_test.go b/rollout/bluegreen_test.go index f8abaa9fcc..d4b11af271 100644 --- a/rollout/bluegreen_test.go +++ b/rollout/bluegreen_test.go @@ -174,6 +174,47 @@ func TestBlueGreenSetPreviewService(t *testing.T) { f.verifyPatchedService(servicePatch, rsPodHash, "") } +// TestBlueGreenSetAdditionalPreviewServices ensures all preview services are set to the desired ReplicaSet +func TestBlueGreenSetAdditionalPreviewServices(t *testing.T) { + previewServiceNames := []string{"preview", "additional1", "additional2", "additional3"} + previewServices := make([]*corev1.Service, len(previewServiceNames)) + f := newFixture(t) + defer f.Close() + + r := newBlueGreenRollout("foo", 1, nil, "active", previewServiceNames[0]) + r.Spec.Strategy.BlueGreen.AdditionalPreviewServices = previewServiceNames[1:] + f.rolloutLister = append(f.rolloutLister, r) + f.objects = append(f.objects, r) + + rs := newReplicaSetWithStatus(r, 1, 1) + rsPodHash := rs.Labels[v1alpha1.DefaultRolloutUniqueLabelKey] + f.kubeobjects = append(f.kubeobjects, rs) + f.replicaSetLister = append(f.replicaSetLister, rs) + + selector := map[string]string{v1alpha1.DefaultRolloutUniqueLabelKey: rsPodHash} + activeSvc := newService("active", 80, selector, r) + f.kubeobjects = append(f.kubeobjects, activeSvc) + for i, svcName := range previewServiceNames { + previewServices[i] = newService(svcName, 80, nil, r) + f.kubeobjects = append(f.kubeobjects, previewServices[i]) + } + + f.serviceLister = append(f.serviceLister, previewServices[0], activeSvc) + f.serviceLister = append(f.serviceLister, previewServices[1:]...) + + servicePatches := make([]int, len(previewServices)) + for i, svc := range previewServices { + servicePatches[i] = f.expectPatchServiceAction(svc, rsPodHash) + } + + f.expectPatchRolloutAction(r) + f.run(getKey(r, t)) + + for _, patch := range servicePatches { + f.verifyPatchedService(patch, rsPodHash, "") + } +} + // TestBlueGreenProgressDeadlineAbort tests aborting an update if it is timeout func TestBlueGreenProgressDeadlineAbort(t *testing.T) { // Two cases to be tested: diff --git a/rollout/service.go b/rollout/service.go index 91bbd39566..fd911aafda 100644 --- a/rollout/service.go +++ b/rollout/service.go @@ -242,6 +242,32 @@ func (c *rolloutContext) getPreviewAndActiveServices() (*corev1.Service, *corev1 return previewSvc, activeSvc, nil } +func (c *rolloutContext) makeServicesList(svcNames []string) ([]*corev1.Service, error) { + svcs := make([]*corev1.Service, len(svcNames)) + for i, svcName := range svcNames { + svc, err := c.servicesLister.Services(c.rollout.Namespace).Get(svcName) + if err != nil { + return nil, err + } + svcs[i] = svc + } + return svcs, nil +} + +func (c *rolloutContext) getAdditionalPreviewAndActiveServices() ([]*corev1.Service, []*corev1.Service, error) { + previewSvcs, err := c.makeServicesList(c.rollout.Spec.Strategy.BlueGreen.AdditionalPreviewServices) + if err != nil { + return nil, nil, err + } + + activeSvcs, err := c.makeServicesList(c.rollout.Spec.Strategy.BlueGreen.AdditionalActiveServices) + if err != nil { + return nil, nil, err + } + + return previewSvcs, activeSvcs, nil +} + func (c *rolloutContext) reconcilePingAndPongService() error { if trafficrouting.IsPingPongEnabled(c.rollout) && !rolloututils.IsFullyPromoted(c.rollout) { _, canaryService := trafficrouting.GetStableAndCanaryServices(c.rollout, true) diff --git a/ui/src/models/rollout/generated/api.ts b/ui/src/models/rollout/generated/api.ts index 642bd611ed..76860ac5e7 100755 --- a/ui/src/models/rollout/generated/api.ts +++ b/ui/src/models/rollout/generated/api.ts @@ -674,6 +674,18 @@ export interface GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1BlueGreenSt * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1BlueGreenStrategy */ abortScaleDownDelaySeconds?: number; + /** + * + * @type {Array} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1BlueGreenStrategy + */ + additionalActiveServices?: Array; + /** + * + * @type {Array} + * @memberof GithubComArgoprojArgoRolloutsPkgApisRolloutsV1alpha1BlueGreenStrategy + */ + additionalPreviewServices?: Array; } /** *