Skip to content

Commit 1754603

Browse files
committed
support updating primary labels/annotations after first time rollout
1 parent c02477a commit 1754603

File tree

8 files changed

+283
-46
lines changed

8 files changed

+283
-46
lines changed

artifacts/flagger/crd.yaml

+33
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,38 @@ spec:
782782
type: object
783783
additionalProperties:
784784
type: string
785+
deployment:
786+
description: Kubernetes Deployment spec
787+
type: object
788+
properties:
789+
primary:
790+
description: Metadata to add to the primary deployment
791+
type: object
792+
properties:
793+
labels:
794+
type: object
795+
additionalProperties:
796+
type: string
797+
annotations:
798+
type: object
799+
additionalProperties:
800+
type: string
801+
autoscaler:
802+
description: Kubernetes Autoscaler spec
803+
type: object
804+
properties:
805+
primary:
806+
description: Metadata to add to the primary autoscaler
807+
type: object
808+
properties:
809+
labels:
810+
type: object
811+
additionalProperties:
812+
type: string
813+
annotations:
814+
type: object
815+
additionalProperties:
816+
type: string
785817
skipAnalysis:
786818
description: Skip analysis and promote canary
787819
type: boolean
@@ -1108,6 +1140,7 @@ spec:
11081140
- cloudwatch
11091141
- newrelic
11101142
- graphite
1143+
- dynatrace
11111144
address:
11121145
description: API address of this provider
11131146
type: string

charts/flagger/crds/crd.yaml

+32
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,38 @@ spec:
782782
type: object
783783
additionalProperties:
784784
type: string
785+
deployment:
786+
description: Kubernetes Deployment spec
787+
type: object
788+
properties:
789+
primary:
790+
description: Metadata to add to the primary deployment
791+
type: object
792+
properties:
793+
labels:
794+
type: object
795+
additionalProperties:
796+
type: string
797+
annotations:
798+
type: object
799+
additionalProperties:
800+
type: string
801+
autoscaler:
802+
description: Kubernetes Autoscaler spec
803+
type: object
804+
properties:
805+
primary:
806+
description: Metadata to add to the primary autoscaler
807+
type: object
808+
properties:
809+
labels:
810+
type: object
811+
additionalProperties:
812+
type: string
813+
annotations:
814+
type: object
815+
additionalProperties:
816+
type: string
785817
skipAnalysis:
786818
description: Skip analysis and promote canary
787819
type: boolean

kustomize/base/flagger/crd.yaml

+32
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,38 @@ spec:
782782
type: object
783783
additionalProperties:
784784
type: string
785+
deployment:
786+
description: Kubernetes Deployment spec
787+
type: object
788+
properties:
789+
primary:
790+
description: Metadata to add to the primary deployment
791+
type: object
792+
properties:
793+
labels:
794+
type: object
795+
additionalProperties:
796+
type: string
797+
annotations:
798+
type: object
799+
additionalProperties:
800+
type: string
801+
autoscaler:
802+
description: Kubernetes Autoscaler spec
803+
type: object
804+
properties:
805+
primary:
806+
description: Metadata to add to the primary autoscaler
807+
type: object
808+
properties:
809+
labels:
810+
type: object
811+
additionalProperties:
812+
type: string
813+
annotations:
814+
type: object
815+
additionalProperties:
816+
type: string
785817
skipAnalysis:
786818
description: Skip analysis and promote canary
787819
type: boolean

pkg/apis/flagger/v1beta1/canary.go

+22
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ type CanarySpec struct {
8585
// Service defines how ClusterIP services, service mesh or ingress routing objects are generated
8686
Service CanaryService `json:"service"`
8787

88+
// Service defines how deployments are generated
89+
Deployment CanaryDeployment `json:"deployment"`
90+
91+
// Service defines how autoscaler are generated
92+
Autoscaler CanaryAutoscaler `json:"autoscaler"`
93+
8894
// Analysis defines the validation process of a release
8995
Analysis *CanaryAnalysis `json:"analysis,omitempty"`
9096

@@ -193,6 +199,22 @@ type CanaryService struct {
193199
Canary *CustomMetadata `json:"canary,omitempty"`
194200
}
195201

202+
// CanaryDeployment defines how deployments are generated
203+
type CanaryDeployment struct {
204+
205+
// Primary is the metadata to add to the primary deployment
206+
// +optional
207+
Primary *CustomMetadata `json:"primary,omitempty"`
208+
}
209+
210+
// CanaryDeployment defines how deployments are generated
211+
type CanaryAutoscaler struct {
212+
213+
// Primary is the metadata to add to the primary autoscaler
214+
// +optional
215+
Primary *CustomMetadata `json:"primary,omitempty"`
216+
}
217+
196218
// CanaryAnalysis is used to describe how the analysis should be done
197219
type CanaryAnalysis struct {
198220
// Schedule interval for this canary analysis

pkg/apis/flagger/v1beta1/zz_generated.deepcopy.go

+44
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/canary/deployment_controller.go

+92-46
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3030
"k8s.io/apimachinery/pkg/runtime/schema"
3131
"k8s.io/client-go/kubernetes"
32+
"k8s.io/client-go/util/retry"
3233

3334
flaggerv1 "github.com/fluxcd/flagger/pkg/apis/flagger/v1beta1"
3435
clientset "github.com/fluxcd/flagger/pkg/client/clientset/versioned"
@@ -84,54 +85,72 @@ func (c *DeploymentController) Promote(cd *flaggerv1.Canary) error {
8485
targetName := cd.Spec.TargetRef.Name
8586
primaryName := fmt.Sprintf("%s-primary", targetName)
8687

87-
canary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(context.TODO(), targetName, metav1.GetOptions{})
88-
if err != nil {
89-
return fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
90-
}
88+
err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
89+
canary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(context.TODO(), targetName, metav1.GetOptions{})
90+
if err != nil {
91+
return fmt.Errorf("deployment %s.%s get query error: %w", targetName, cd.Namespace, err)
92+
}
9193

92-
label, labelValue, err := c.getSelectorLabel(canary)
93-
primaryLabelValue := fmt.Sprintf("%s-primary", labelValue)
94-
if err != nil {
95-
return fmt.Errorf("getSelectorLabel failed: %w", err)
96-
}
94+
label, labelValue, err := c.getSelectorLabel(canary)
95+
primaryLabelValue := fmt.Sprintf("%s-primary", labelValue)
96+
if err != nil {
97+
return fmt.Errorf("getSelectorLabel failed: %w", err)
98+
}
9799

98-
primary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(context.TODO(), primaryName, metav1.GetOptions{})
99-
if err != nil {
100-
return fmt.Errorf("deployment %s.%s get query error: %w", primaryName, cd.Namespace, err)
101-
}
100+
primary, err := c.kubeClient.AppsV1().Deployments(cd.Namespace).Get(context.TODO(), primaryName, metav1.GetOptions{})
101+
if err != nil {
102+
return fmt.Errorf("deployment %s.%s get query error: %w", primaryName, cd.Namespace, err)
103+
}
102104

103-
// promote secrets and config maps
104-
configRefs, err := c.configTracker.GetTargetConfigs(cd)
105-
if err != nil {
106-
return fmt.Errorf("GetTargetConfigs failed: %w", err)
107-
}
108-
if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs, c.includeLabelPrefix); err != nil {
109-
return fmt.Errorf("CreatePrimaryConfigs failed: %w", err)
110-
}
105+
// promote secrets and config maps
106+
configRefs, err := c.configTracker.GetTargetConfigs(cd)
107+
if err != nil {
108+
return fmt.Errorf("GetTargetConfigs failed: %w", err)
109+
}
110+
if err := c.configTracker.CreatePrimaryConfigs(cd, configRefs, c.includeLabelPrefix); err != nil {
111+
return fmt.Errorf("CreatePrimaryConfigs failed: %w", err)
112+
}
111113

112-
primaryCopy := primary.DeepCopy()
113-
primaryCopy.Spec.ProgressDeadlineSeconds = canary.Spec.ProgressDeadlineSeconds
114-
primaryCopy.Spec.MinReadySeconds = canary.Spec.MinReadySeconds
115-
primaryCopy.Spec.RevisionHistoryLimit = canary.Spec.RevisionHistoryLimit
116-
primaryCopy.Spec.Strategy = canary.Spec.Strategy
114+
primaryCopy := primary.DeepCopy()
115+
primaryCopy.Spec.ProgressDeadlineSeconds = canary.Spec.ProgressDeadlineSeconds
116+
primaryCopy.Spec.MinReadySeconds = canary.Spec.MinReadySeconds
117+
primaryCopy.Spec.RevisionHistoryLimit = canary.Spec.RevisionHistoryLimit
118+
primaryCopy.Spec.Strategy = canary.Spec.Strategy
117119

118-
// update spec with primary secrets and config maps
119-
primaryCopy.Spec.Template.Spec = c.getPrimaryDeploymentTemplateSpec(canary, configRefs)
120+
// update spec with primary secrets and config maps
121+
primaryCopy.Spec.Template.Spec = c.getPrimaryDeploymentTemplateSpec(canary, configRefs)
120122

121-
// update pod annotations to ensure a rolling update
122-
annotations, err := makeAnnotations(canary.Spec.Template.Annotations)
123-
if err != nil {
124-
return fmt.Errorf("makeAnnotations failed: %w", err)
125-
}
123+
// update pod annotations to ensure a rolling update
124+
annotations, err := makeAnnotations(canary.Spec.Template.Annotations)
125+
if err != nil {
126+
return fmt.Errorf("makeAnnotations failed: %w", err)
127+
}
126128

127-
primaryCopy.Spec.Template.Annotations = annotations
128-
primaryCopy.Spec.Template.Labels = makePrimaryLabels(canary.Spec.Template.Labels, primaryLabelValue, label)
129+
primaryCopy.Spec.Template.Annotations = annotations
130+
primaryCopy.Spec.Template.Labels = makePrimaryLabels(canary.Spec.Template.Labels, primaryLabelValue, label)
129131

130-
// apply update
131-
_, err = c.kubeClient.AppsV1().Deployments(cd.Namespace).Update(context.TODO(), primaryCopy, metav1.UpdateOptions{})
132+
if cd.Spec.Deployment.Primary != nil {
133+
if len(primaryCopy.ObjectMeta.Annotations) == 0 {
134+
primaryCopy.ObjectMeta.Annotations = make(map[string]string)
135+
}
136+
for k, v := range cd.Spec.Deployment.Primary.Annotations {
137+
primaryCopy.ObjectMeta.Annotations[k] = v
138+
}
139+
if len(primaryCopy.ObjectMeta.Labels) == 0 {
140+
primaryCopy.ObjectMeta.Labels = make(map[string]string)
141+
}
142+
for k, v := range cd.Spec.Deployment.Primary.Labels {
143+
primaryCopy.ObjectMeta.Labels[k] = v
144+
}
145+
}
146+
147+
// apply update
148+
_, err = c.kubeClient.AppsV1().Deployments(cd.Namespace).Update(context.TODO(), primaryCopy, metav1.UpdateOptions{})
149+
return err
150+
})
132151
if err != nil {
133152
return fmt.Errorf("updating deployment %s.%s template spec failed: %w",
134-
primaryCopy.GetName(), primaryCopy.Namespace, err)
153+
primaryName, cd.Namespace, err)
135154
}
136155

137156
// update HPA
@@ -364,18 +383,45 @@ func (c *DeploymentController) reconcilePrimaryHpa(cd *flaggerv1.Canary, init bo
364383
if !init && primaryHpa != nil {
365384
diffMetrics := cmp.Diff(hpaSpec.Metrics, primaryHpa.Spec.Metrics)
366385
diffBehavior := cmp.Diff(hpaSpec.Behavior, primaryHpa.Spec.Behavior)
367-
if diffMetrics != "" || diffBehavior != "" || int32Default(hpaSpec.MinReplicas) != int32Default(primaryHpa.Spec.MinReplicas) || hpaSpec.MaxReplicas != primaryHpa.Spec.MaxReplicas {
386+
diffLabels := cmp.Diff(hpa.ObjectMeta.Labels, primaryHpa.ObjectMeta.Labels)
387+
diffAnnotations := cmp.Diff(hpa.ObjectMeta.Annotations, primaryHpa.ObjectMeta.Annotations)
388+
if diffMetrics != "" || diffBehavior != "" || diffLabels != "" || diffAnnotations != "" || int32Default(hpaSpec.MinReplicas) != int32Default(primaryHpa.Spec.MinReplicas) || hpaSpec.MaxReplicas != primaryHpa.Spec.MaxReplicas {
368389
fmt.Println(diffMetrics, diffBehavior, hpaSpec.MinReplicas, primaryHpa.Spec.MinReplicas, hpaSpec.MaxReplicas, primaryHpa.Spec.MaxReplicas)
369-
hpaClone := primaryHpa.DeepCopy()
370-
hpaClone.Spec.MaxReplicas = hpaSpec.MaxReplicas
371-
hpaClone.Spec.MinReplicas = hpaSpec.MinReplicas
372-
hpaClone.Spec.Metrics = hpaSpec.Metrics
373-
hpaClone.Spec.Behavior = hpaSpec.Behavior
374390

375-
_, err := c.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Update(context.TODO(), hpaClone, metav1.UpdateOptions{})
391+
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
392+
primaryHpa, err := c.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Get(context.TODO(), primaryHpaName, metav1.GetOptions{})
393+
if err != nil {
394+
return err
395+
}
396+
397+
hpaClone := primaryHpa.DeepCopy()
398+
hpaClone.Spec.MaxReplicas = hpaSpec.MaxReplicas
399+
hpaClone.Spec.MinReplicas = hpaSpec.MinReplicas
400+
hpaClone.Spec.Metrics = hpaSpec.Metrics
401+
hpaClone.Spec.Behavior = hpaSpec.Behavior
402+
403+
if cd.Spec.Autoscaler.Primary != nil {
404+
if len(hpaClone.ObjectMeta.Annotations) == 0 {
405+
hpaClone.ObjectMeta.Annotations = make(map[string]string)
406+
}
407+
for k, v := range cd.Spec.Autoscaler.Primary.Annotations {
408+
hpaClone.ObjectMeta.Annotations[k] = v
409+
}
410+
if len(hpaClone.ObjectMeta.Labels) == 0 {
411+
hpaClone.ObjectMeta.Labels = make(map[string]string)
412+
}
413+
for k, v := range cd.Spec.Autoscaler.Primary.Labels {
414+
hpaClone.ObjectMeta.Labels[k] = v
415+
}
416+
}
417+
418+
_, err = c.kubeClient.AutoscalingV2beta2().HorizontalPodAutoscalers(cd.Namespace).Update(context.TODO(), hpaClone, metav1.UpdateOptions{})
419+
return err
420+
})
421+
376422
if err != nil {
377423
return fmt.Errorf("updating HorizontalPodAutoscaler %s.%s failed: %w",
378-
hpaClone.Name, hpaClone.Namespace, err)
424+
primaryHpa.Name, primaryHpa.Namespace, err)
379425
}
380426
c.logger.With("canary", fmt.Sprintf("%s.%s", cd.Name, cd.Namespace)).
381427
Infof("HorizontalPodAutoscaler %s.%s updated", primaryHpa.GetName(), cd.Namespace)

0 commit comments

Comments
 (0)