@@ -11,16 +11,19 @@ import (
11
11
"golang.org/x/exp/maps"
12
12
appsV1 "k8s.io/api/apps/v1"
13
13
corev1 "k8s.io/api/core/v1"
14
+ policyv1 "k8s.io/api/policy/v1"
14
15
"k8s.io/apimachinery/pkg/api/errors"
15
16
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16
17
"k8s.io/apimachinery/pkg/util/intstr"
18
+ "k8s.io/client-go/util/retry"
17
19
"sigs.k8s.io/controller-runtime/pkg/client"
18
20
)
19
21
20
22
const (
21
- FlagdProxyDeploymentName = "flagd-proxy"
22
- FlagdProxyServiceAccountName = "open-feature-operator-flagd-proxy"
23
- FlagdProxyServiceName = "flagd-proxy-svc"
23
+ FlagdProxyDeploymentName = "flagd-proxy"
24
+ FlagdProxyServiceAccountName = "open-feature-operator-flagd-proxy"
25
+ FlagdProxyServiceName = "flagd-proxy-svc"
26
+ FlagdProxyPodDisruptionBudgetName = "flagd-proxy-pdb"
24
27
)
25
28
26
29
type FlagdProxyHandler struct {
@@ -37,6 +40,7 @@ type FlagdProxyConfiguration struct {
37
40
DebugLogging bool
38
41
Image string
39
42
Tag string
43
+ Replicas int
40
44
Namespace string
41
45
OperatorDeploymentName string
42
46
ImagePullSecrets []string
@@ -53,6 +57,7 @@ func NewFlagdProxyConfiguration(env types.EnvConfig, imagePullSecrets []string,
53
57
Port : env .FlagdProxyPort ,
54
58
ManagementPort : env .FlagdProxyManagementPort ,
55
59
DebugLogging : env .FlagdProxyDebugLogging ,
60
+ Replicas : env .FlagdProxyReplicaCount ,
56
61
ImagePullSecrets : imagePullSecrets ,
57
62
Labels : labels ,
58
63
Annotations : annotations ,
@@ -71,58 +76,99 @@ func (f *FlagdProxyHandler) Config() *FlagdProxyConfiguration {
71
76
return f .config
72
77
}
73
78
74
- func (f * FlagdProxyHandler ) createObject (ctx context.Context , obj client.Object ) error {
75
- return f .Client .Create (ctx , obj )
79
+ func specDiffers (a , b client.Object ) (bool , error ) {
80
+ if a == nil || b == nil {
81
+ return false , fmt .Errorf ("object is nil" )
82
+ }
83
+
84
+ // Compare only spec based on the object type
85
+ switch a .(type ) {
86
+ case * corev1.Service :
87
+ return ! reflect .DeepEqual (a .(* corev1.Service ).Spec , b .(* corev1.Service ).Spec ), nil
88
+ case * appsV1.Deployment :
89
+ return ! reflect .DeepEqual (a .(* appsV1.Deployment ).Spec , b .(* appsV1.Deployment ).Spec ), nil
90
+ case * policyv1.PodDisruptionBudget :
91
+ return ! reflect .DeepEqual (a .(* policyv1.PodDisruptionBudget ).Spec , b .(* policyv1.PodDisruptionBudget ).Spec ), nil
92
+ default :
93
+ return false , fmt .Errorf ("unsupported object type" )
94
+ }
76
95
}
77
96
78
- func (f * FlagdProxyHandler ) updateObject (ctx context.Context , obj client.Object ) error {
79
- return f .Client .Update (ctx , obj )
97
+ // ensureFlagdProxyResource ensures that the given object is reconciled in the cluster. If the object does not exist, it will be created.
98
+ func (f * FlagdProxyHandler ) ensureFlagdProxyResource (ctx context.Context , obj client.Object ) error {
99
+ if obj == nil {
100
+ return fmt .Errorf ("object is nil" )
101
+ }
102
+
103
+ return retry .RetryOnConflict (retry .DefaultRetry , func () error {
104
+ var old = obj .DeepCopyObject ().(client.Object )
105
+ f .Log .Info ("Ensuring object exists" , "name" , obj .GetName (), "namespace" , obj .GetNamespace ())
106
+
107
+ // Try to get the existing object
108
+ err := f .Client .Get (ctx , client.ObjectKey {Name : old .GetName (), Namespace : old .GetNamespace ()}, old )
109
+ notFound := errors .IsNotFound (err )
110
+ if err != nil && ! notFound {
111
+ return err
112
+ }
113
+
114
+ // If the object is not found, we will create it
115
+ if notFound {
116
+ return f .Client .Create (ctx , obj )
117
+ }
118
+ // If the object exists but is not managed by OFO, return an error
119
+ if ! common .IsManagedByOFO (old ) {
120
+ return fmt .Errorf ("%s not managed by OFO" , obj .GetName ())
121
+ }
122
+
123
+ // If the object is found, update if necessary
124
+ needsUpdate , err := specDiffers (obj , old )
125
+ if err != nil {
126
+ return err
127
+ }
128
+
129
+ if needsUpdate {
130
+ obj .SetResourceVersion (old .GetResourceVersion ())
131
+ return f .Client .Update (ctx , obj )
132
+ }
133
+
134
+ return nil
135
+ })
80
136
}
81
137
138
+ // HandleFlagdProxy ensures flagd-proxy kubernetes components are configured properly
82
139
func (f * FlagdProxyHandler ) HandleFlagdProxy (ctx context.Context ) error {
83
- exists , deployment , err := f .doesFlagdProxyExist (ctx )
84
- if err != nil {
85
- return err
86
- }
140
+ var err error
87
141
88
- ownerReference , err := f .getOwnerReference (ctx )
142
+ ownerRef , err := f .getOwnerReference (ctx )
89
143
if err != nil {
90
144
return err
91
145
}
92
- newDeployment := f .newFlagdProxyManifest (ownerReference )
93
- newService := f .newFlagdProxyServiceManifest (ownerReference )
94
-
95
- if ! exists {
96
- f .Log .Info ("flagd-proxy Deployment does not exist, creating" )
97
- return f .deployFlagdProxy (ctx , f .createObject , newDeployment , newService )
98
- }
99
- // flagd-proxy exists, need to check if we should update it
100
- if f .shouldUpdateFlagdProxy (deployment , newDeployment ) {
101
- f .Log .Info ("flagd-proxy Deployment out of sync, updating" )
102
- return f .deployFlagdProxy (ctx , f .updateObject , newDeployment , newService )
103
- }
104
- f .Log .Info ("flagd-proxy Deployment up-to-date" )
105
- return nil
106
- }
107
146
108
- func (f * FlagdProxyHandler ) deployFlagdProxy (ctx context.Context , createUpdateFunc CreateUpdateFunc , deployment * appsV1.Deployment , service * corev1.Service ) error {
109
- f .Log .Info ("deploying the flagd-proxy" )
110
- if err := createUpdateFunc (ctx , deployment ); err != nil && ! errors .IsAlreadyExists (err ) {
147
+ if err = f .ensureFlagdProxyResource (ctx , f .newFlagdProxyDeployment (ownerRef )); err != nil {
111
148
return err
112
149
}
113
- f . Log . Info ( "deploying the flagd-proxy service" )
114
- if err := createUpdateFunc (ctx , service ) ; err != nil && ! errors . IsAlreadyExists ( err ) {
150
+
151
+ if err = f . ensureFlagdProxyResource (ctx , f . newFlagdProxyService ( ownerRef )) ; err != nil {
115
152
return err
116
153
}
117
- return nil
154
+
155
+ err = f .ensureFlagdProxyResource (ctx , f .newFlagdProxyPodDisruptionBudget (ownerRef ))
156
+ return err
118
157
}
119
158
120
- func (f * FlagdProxyHandler ) newFlagdProxyServiceManifest (ownerReference * metav1.OwnerReference ) * corev1.Service {
159
+ func (f * FlagdProxyHandler ) newFlagdProxyService (ownerReference * metav1.OwnerReference ) * corev1.Service {
121
160
return & corev1.Service {
161
+ TypeMeta : metav1.TypeMeta {
162
+ Kind : "Service" ,
163
+ APIVersion : "v1" ,
164
+ },
122
165
ObjectMeta : metav1.ObjectMeta {
123
166
Name : FlagdProxyServiceName ,
124
167
Namespace : f .config .Namespace ,
125
168
OwnerReferences : []metav1.OwnerReference {* ownerReference },
169
+ Labels : map [string ]string {
170
+ common .ManagedByAnnotationKey : common .ManagedByAnnotationValue ,
171
+ },
126
172
},
127
173
Spec : corev1.ServiceSpec {
128
174
Selector : map [string ]string {
@@ -140,8 +186,41 @@ func (f *FlagdProxyHandler) newFlagdProxyServiceManifest(ownerReference *metav1.
140
186
}
141
187
}
142
188
143
- func (f * FlagdProxyHandler ) newFlagdProxyManifest (ownerReference * metav1.OwnerReference ) * appsV1.Deployment {
144
- replicas := int32 (1 )
189
+ func (f * FlagdProxyHandler ) newFlagdProxyPodDisruptionBudget (ownerReference * metav1.OwnerReference ) * policyv1.PodDisruptionBudget {
190
+
191
+ // Only require pods to be available if there is >1 replica configured (HA setup)
192
+ minReplicas := intstr .FromInt (0 )
193
+ if f .config .Replicas > 1 {
194
+ minReplicas = intstr .FromInt (f .config .Replicas / 2 )
195
+ }
196
+
197
+ return & policyv1.PodDisruptionBudget {
198
+ TypeMeta : metav1.TypeMeta {
199
+ Kind : "PodDisruptionBudget" ,
200
+ APIVersion : "policy/v1" ,
201
+ },
202
+ ObjectMeta : metav1.ObjectMeta {
203
+ Name : FlagdProxyPodDisruptionBudgetName ,
204
+ Namespace : f .config .Namespace ,
205
+ OwnerReferences : []metav1.OwnerReference {* ownerReference },
206
+ Labels : map [string ]string {
207
+ common .ManagedByAnnotationKey : common .ManagedByAnnotationValue ,
208
+ },
209
+ },
210
+ Spec : policyv1.PodDisruptionBudgetSpec {
211
+ MinAvailable : & minReplicas ,
212
+ Selector : & metav1.LabelSelector {
213
+ MatchLabels : map [string ]string {
214
+ "app.kubernetes.io/name" : FlagdProxyDeploymentName ,
215
+ common .ManagedByAnnotationKey : common .ManagedByAnnotationValue ,
216
+ },
217
+ },
218
+ },
219
+ }
220
+ }
221
+
222
+ func (f * FlagdProxyHandler ) newFlagdProxyDeployment (ownerReference * metav1.OwnerReference ) * appsV1.Deployment {
223
+ replicas := int32 (f .config .Replicas )
145
224
args := []string {
146
225
"start" ,
147
226
"--management-port" ,
@@ -157,10 +236,10 @@ func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReference *metav1.OwnerRe
157
236
})
158
237
}
159
238
flagdLabels := map [string ]string {
160
- "app" : FlagdProxyDeploymentName ,
161
- "app.kubernetes.io/name" : FlagdProxyDeploymentName ,
162
- "app.kubernetes.io/managed-by" : common .ManagedByAnnotationValue ,
163
- "app.kubernetes.io/version" : f .config .Tag ,
239
+ "app" : FlagdProxyDeploymentName ,
240
+ "app.kubernetes.io/name" : FlagdProxyDeploymentName ,
241
+ common . ManagedByAnnotationKey : common .ManagedByAnnotationValue ,
242
+ "app.kubernetes.io/version" : f .config .Tag ,
164
243
}
165
244
if len (f .config .Labels ) > 0 {
166
245
maps .Copy (flagdLabels , f .config .Labels )
@@ -173,13 +252,17 @@ func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReference *metav1.OwnerRe
173
252
}
174
253
175
254
return & appsV1.Deployment {
255
+ TypeMeta : metav1.TypeMeta {
256
+ Kind : "Deployment" ,
257
+ APIVersion : "apps/v1" ,
258
+ },
176
259
ObjectMeta : metav1.ObjectMeta {
177
260
Name : FlagdProxyDeploymentName ,
178
261
Namespace : f .config .Namespace ,
179
262
Labels : map [string ]string {
180
- "app" : FlagdProxyDeploymentName ,
181
- "app.kubernetes.io/managed-by" : common .ManagedByAnnotationValue ,
182
- "app.kubernetes.io/version" : f .config .Tag ,
263
+ "app" : FlagdProxyDeploymentName ,
264
+ common . ManagedByAnnotationKey : common .ManagedByAnnotationValue ,
265
+ "app.kubernetes.io/version" : f .config .Tag ,
183
266
},
184
267
OwnerReferences : []metav1.OwnerReference {* ownerReference },
185
268
},
@@ -215,41 +298,31 @@ func (f *FlagdProxyHandler) newFlagdProxyManifest(ownerReference *metav1.OwnerRe
215
298
Args : args ,
216
299
},
217
300
},
301
+ TopologySpreadConstraints : []corev1.TopologySpreadConstraint {
302
+ {
303
+ MaxSkew : 1 ,
304
+ TopologyKey : "kubernetes.io/hostname" ,
305
+ WhenUnsatisfiable : corev1 .DoNotSchedule ,
306
+ LabelSelector : & metav1.LabelSelector {
307
+ MatchLabels : map [string ]string {
308
+ "app.kubernetes.io/name" : FlagdProxyDeploymentName ,
309
+ common .ManagedByAnnotationKey : common .ManagedByAnnotationValue ,
310
+ },
311
+ },
312
+ },
313
+ },
218
314
},
219
315
},
220
316
},
221
317
}
222
318
}
223
319
224
- func (f * FlagdProxyHandler ) doesFlagdProxyExist (ctx context.Context ) (bool , * appsV1.Deployment , error ) {
225
- d := & appsV1.Deployment {}
226
- err := f .Client .Get (ctx , client.ObjectKey {Name : FlagdProxyDeploymentName , Namespace : f .config .Namespace }, d )
227
- if err != nil {
228
- if errors .IsNotFound (err ) {
229
- // does not exist, is not ready, no error
230
- return false , nil , nil
231
- }
232
- // does not exist, is not ready, is in error
233
- return false , nil , err
234
- }
235
- return true , d , nil
236
- }
237
-
238
- func (f * FlagdProxyHandler ) shouldUpdateFlagdProxy (old , new * appsV1.Deployment ) bool {
239
- if ! common .IsManagedByOFO (old ) {
240
- f .Log .Info ("flagd-proxy Deployment not managed by OFO" )
241
- return false
242
- }
243
- return ! reflect .DeepEqual (old .Spec , new .Spec )
244
- }
245
-
246
320
func (f * FlagdProxyHandler ) getOperatorDeployment (ctx context.Context ) (* appsV1.Deployment , error ) {
247
321
d := & appsV1.Deployment {}
248
322
if err := f .Client .Get (ctx , client.ObjectKey {Name : f .config .OperatorDeploymentName , Namespace : f .config .Namespace }, d ); err != nil {
249
323
return nil , fmt .Errorf ("unable to fetch operator deployment: %w" , err )
250
324
}
251
325
return d , nil
252
-
253
326
}
254
327
255
328
func (f * FlagdProxyHandler ) getOwnerReference (ctx context.Context ) (* metav1.OwnerReference , error ) {
0 commit comments