Skip to content

Commit 0135f48

Browse files
committed
Removed suspend scheduling by annotation from the scope of the rfc.
Added discussion and implementation samples for update predicate. Altered plans for handling suspended objects only by annotation and omitting any status updates or .spec.suspend manipulation. Signed-off-by: Travis Mattera <[email protected]>
1 parent fa42c8b commit 0135f48

File tree

1 file changed

+116
-61
lines changed
  • rfcs/0006-alternative-suspend-control

1 file changed

+116
-61
lines changed

rfcs/0006-alternative-suspend-control/README.md

Lines changed: 116 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@
44

55
**Creation date:** 2023-09-20
66

7-
**Last update:** 2023-09-20
7+
**Last update:** 2023-10-10
88

99

1010
## Summary
1111

12-
This RFC proposes an alternative method to indicate the suspended state of suspendable resources to flux controllers through object metadata. It presents an annotation key that can support suspending indefinitely or according to a schedule. It does not address the deprecation of the the `.spec.suspend` field from the resource apis.
12+
This RFC proposes an alternative method to indicate the suspended state of suspendable resources to flux controllers through object metadata. It presents an annotation key that can be used to suspend a resource from reconciliation as an alternative to the `.spec.suspend` field. It does not address the deprecation of the the `.spec.suspend` field from the resource apis. This annotation can optionally act as a vehicle for communicating contextual information about the suspended resource to users.
1313

1414

1515
## Motivation
1616

1717
The current implementation of suspending a resource from reconciliation uses the `.spec.suspend` field. A change to this field results in a generation number increase which can be confusing when diffing.
1818

19-
Teams may want to set windows during which no gitops reconciliations happen for a resource. Such hold-offs are a natural fit for the existing suspend functionality which could be augmented to support date-time ranges.
19+
Teams may wish to communicate information about the suspended resource, such as the reason for the suspension, in the object itself.
2020

2121
### Goals
2222

23-
The flux reconciliation loop will support recognizing a resource's suspend status based on either the `.spec.suspend` field or a specific annotation metadata key. The flux cli will similarly recognize this with get commands and will manipulate both the resource `.spec.suspend` field and the suspend status under suspend and resume commands.
23+
The flux reconciliation loop will support recognizing a resource's suspend status based on either the `.spec.suspend` field or a specific metadata annotation key. The flux cli will similarly recognize this state with `get` commands and but will only manipulate the annotation key under `suspend` and `resume` commands. The flux cli will support optionally setting the suspend metadata annotation value with a user supplied string for a contextual message.
2424

2525
### Non-Goals
2626

@@ -29,9 +29,7 @@ The deprecation plan for the `.spec.suspend` field is out of scope for this RFC.
2929

3030
## Proposal
3131

32-
Register a resource object status for the suspended state. The flux object status `suspended` holds the current state of suspension from reconciliation for the resource.
33-
34-
Register a flux resource metadata key with a temporal suspend semantic with suspend status handling implemented in each of the controllers and in the flux cli. The annotation `reconcile.fluxcd.io/suspendedDuring` defines when to suspend a resource through a period of time. Supports a cron format including tags and ranges. It's presence is used by flux controllers to evaluate and set the suspend status of the resource. Is removed by a resume command.
32+
Register a flux resource metadata key `reconcile.fluxcd.io/suspended` with a suspend semantic to be interpreted by controllers and manipulated by the cli. The presence of the annotation key is an alternative to the `.spec.suspend: true` api field setting when considering if a resource is suspended or not. The annotation key is set by a `flux suspend` command and removed by a `flux resume` command. The annotation key value is open for communicating a message or reason for the object's suspension. The value can be set using a `--message` flag to the `suspend` command.
3533

3634
### User Stories
3735

@@ -41,21 +39,17 @@ Currently when a resource is set to suspended or resumed the `.spec.suspend` fie
4139

4240
The flux controllers should recognize that a resource is suspended or unsuspended from the presence of a special metadata key -- this key can be added, removed or changed without patching the object and rolling the generation number.
4341

44-
#### Suspend During Windows
45-
46-
A cluster manager wishes to set periodic windows during which deploys are held back (eg for stability, release control, maintenance, weekends, etc). Gitops synchronization happens naturally outside of these times.
47-
48-
The cluster manager should be able to define these windows on each resource using a well-known format applied as a metadata key.
49-
5042
#### Seeing Suspend State
5143

5244
Users should be able to see the effective suspend state of the resource with a `flux get` command. The display should mirror what the controllers interpret the suspend state to be. This story is included to capture current functionality that should be preserved.
5345

54-
### Alternatives
46+
#### Suspend with a Reason
5547

56-
#### The Gate
48+
Often there is a purpose behind suspending a resource with the flux cli, whether it be during incident response, source manifest cutovers, or various other scenarios. The `flux diff` command provides a great UX for determining what will change if a suspended resource is resumed, but it doesn't help explain _why_ something is paused or when it would be ok to resume reconciliation. On distributed teams this can become a point of friction as it needs to be communicated among group stakeholders.
5749

58-
The [gate](https://github.com/fluxcd/flux2/pull/3158) proposes a new paired resource and controller to effect a paused reconciliation of selected resources (eg `GitRepository` or `HelmRelease`).
50+
Flux users should have a way to succinctly signal to other users why a resource is suspended on the resource itself.
51+
52+
### Alternatives
5953

6054
#### More `.spec`
6155

@@ -64,60 +58,92 @@ The existing `.spec.suspend` could be expanded with fields for the above semanti
6458

6559
## Design Details
6660

67-
Implementing this RFC would involve resource object status, controllers, cli and metrics.
61+
Implementing this RFC would involve the controllers and the cli.
6862

6963
This feature would create an alternate path to suspending an object and would not violate the current apis.
7064

71-
### Resource Object Status
65+
### Common
7266

73-
Object status could be augmented with a `suspended: true | false` indicating to hold the suspend status of the resource.
67+
The `reconcile.fluxcd.io/suspended` annotation key string and a getter function would be made avaiable for controllers the cli to recognize and manipulate the suspend object metadata.
7468

7569
```
7670
# github.com/fluxcd/pkg/apis/meta
7771
78-
// SuspendDuringAnnotation is the annotation used to set a suspend timedate expression
79-
// the expression would conform to unix-like cron format to allow for indefinite (eg `@always`)
80-
// or scheduled (eg `* 0-4 * * *`) suspend periods.
81-
const SuspendDuringAnnotation string = "reconcile.fluxcd.io/suspendDuring"
8272
83-
type SuspendedStatus struct {
84-
// SuspendedStatus represents the state of reconciliation suspension
85-
Suspended bool `json:"suspended,omitempty"`
73+
// SuspendAnnotation is the annotation used to indicate an object is suspended and optionally to store metadata about why a resource
74+
// was set to suspended.
75+
const SuspendedAnnotation string = "reconcile.fluxcd.io/suspended"
76+
77+
// SuspendAnnotationValue returns a value for the suspended annotation, which can be used to detect
78+
// changes; and, a boolean indicating whether the annotation was set.
79+
func SuspendedAnnotationValue(annotations map[string]string) (string, bool) {
80+
suspend, ok := annotations[SuspendedAnnotation]
81+
return suspend, ok
8682
}
8783
88-
// GetSuspended returns the current suspended state value from the SuspendedStatus.
89-
func (in SuspendedStatus) GetSuspended() string {
90-
return in.Suspended
84+
// SetSuspendedAnnotation sets a suspend key with a supplied string value in an object's metadata annotations
85+
func SetSuspendedAnnotation(annotations *map[string]string, token string) {
86+
if annotations == nil {
87+
annotations = &map[string]string{}
88+
}
89+
if token != "" {
90+
(*annotations)[SuspendedAnnotation] = token
91+
} else {
92+
(*annotations)[SuspendedAnnotation] = "true"
93+
}
9194
}
9295
93-
// SetSuspended sets the suspended state value in the SuspendedStatus.
94-
func (in *SuspendedStatus) SetSuspended(token bool) {
95-
in.Suspended = token
96+
// UnsetSuspendedAnnotation removes a suspend key from an object's metadata annotations
97+
func UnsetSuspendedAnnotation(annotations *map[string]string) {
98+
delete(*annotations, SuspendedAnnotation)
9699
}
97100
```
98101

99-
### Controllers
102+
An event predicate would skip attempting to reconcile a resource with the suspend annotation set.
100103

101-
Flux controllers would set this status based on an `OR` of (1) the api `.spec.suspend` and (2) the evalution of the reconcile time against the suspend metadata annotation expression. The controllers would use this resulting status when deciding to synchronize the resource.
104+
```
105+
# github.com/fluxcd/pkg/apis/predicates
106+
107+
# suspended.go
102108
103-
Coupling the `.spec.suspend` and metadata annotations would be optional. Having the controllers add a suspend metadata annotation, if it did not exist, based on `.spec.suspend: true` would tightly couple the two control points and would provide for a path to deprecating the `.spec.suspend` from the api. The examples below do not show a coupled implementation.
109+
// Update implements the default UpdateEvent filter for filtering by meta.SuspendedAnnotation existence
110+
func (SuspendedPredicate) Update(e event.UpdateEvent) bool {
111+
if e.ObjectOld == nil || e.ObjectNew == nil {
112+
return false
113+
}
104114
115+
if _, ok := metav1.SuspendedAnnotationValue(e.ObjectNew.GetAnnotations()); !ok {
116+
return true
117+
}
118+
return false
119+
}
105120
```
106-
# github.com/fluxcd/kustomize-controller/controller
107121

108-
# kustomization_controller.go
122+
### Controllers
123+
124+
Flux controllers would skip reconciling a resource based on an `OR` of (1) the api `.spec.suspend` and (2) the existence of the suspend metadata annotation key.
109125

110-
// use a cron expression in the suspendDuring annotation to determine if the resource should be set to suspended
111-
// where and how to implement the notional `TimeInPeriod` function is open for suggestion
112-
if obj.spec.Suspend || TimeInPeriod(reconcileStart, obj.Annotations[meta.SuspendedDuring]) {
113-
obj.Status.Suspended = true
114-
} else {
115-
obj.Status.Suspended = false
126+
```
127+
# github.com/fluxcd/kustomize-controller/api/v1
128+
# kustomization_types.go
129+
130+
// IsSuspended returns the effective suspend state of the object.
131+
func (in Kustomization) IsSuspended() bool {
132+
if in.Spec.Suspend {
133+
return true
134+
}
135+
if _, ok := meta.SuspendedAnnotationValue(in.Annotations); ok {
136+
return true
137+
}
138+
return false
116139
}
117140
141+
# github.com/fluxcd/kustomize-controller/controller
142+
# kustomization_controller.go
143+
118144
// Skip reconciliation if the object is suspended.
119145
// if obj.Spec.Suspend { // no longer using `.spec.suspend` directly
120-
if obj.Status.Suspended {
146+
if obj.Status.IsSuspended {
121147
log.Info("Reconciliation is suspended for this object")
122148
return ctrl.Result{}, nil
123149
}
@@ -136,44 +162,73 @@ func (a *gitRepositoryListAdapter) summariseItem(i int, includeNamespace bool, i
136162
...
137163
return append(nameColumns(&item, includeNamespace, includeKind),
138164
// revision, strings.Title(strconv.FormatBool(item.Spec.Suspend)), status, msg)
139-
revision, strings.Title(strconv.FormatBool(item.Status.Suspended)), status, msg)
165+
revision, strings.Title(strconv.FormatBool(item.IsSuspended)), status, msg)
140166
}
141167
```
142168

143-
The flux cli would manipulate the suspend status in addition to the normal `.spec.suspend` toggling behavior. Though the controllers would set this status on the next reconciliation, subsequent intersitial `flux get` commands would not report this status change.
144-
145-
Similar to the controllers discussion above, the flux cli could optionally couple the `.spec.suspend` and suspend metadata annotations mutations. The examples below do not show a coupled implementation.
169+
The flux cli would manipulate the suspend metadata but forgo toggling the `.spec.suspend` setting. An optional `--message|-m` flag would support setting the suspended annotation value to a user-specified string.
146170

147171
```
148172
# github.com/fluxcd/flux2/main
149173
174+
# suspend.go
175+
176+
type SuspendFlags struct {
177+
...
178+
message string
179+
}
180+
181+
func init() {
182+
...
183+
suspendCmd.PersistentFlags().StringVarP(&suspendArgs.message, "message", "m", "",
184+
"set a message about why the resource is suspended, the message will show up under the reconcile.fluxcd.io/suspended annotation")
185+
...
186+
}
187+
188+
type suspendable interface {
189+
...
190+
// setSuspended()
191+
setSuspended(message string)
192+
}
193+
194+
type suspendCommand struct {
195+
...
196+
// obj.setSuspended()
197+
obj.setSuspended(suspendArgs.message)
198+
}
199+
150200
# suspend_helmrelease.go
151201
152-
func (obj helmReleaseAdapter) setSuspended() {
153-
obj.HelmRelease.Spec.Suspend = true
154-
obj.HelmRelease.Status.SetSuspended(true)
202+
func (obj helmReleaseAdapter) isSuspended() bool {
203+
return obj.HelmRelease.IsSuspended() // use the resource api spec method
204+
}
205+
206+
func (obj helmReleaseAdapter) setSuspended(message string) {
207+
meta.SetSuspendedAnnotation(&obj.HelmRelease.Annotations, message) // use the common annotation setter
155208
}
156209
157210
# resume_helmrelease.go
158211
159212
func (obj helmReleaseAdapter) setUnsuspended() {
160-
obj.HelmRelease.Spec.Suspend = false
161-
obj.HelmRelease.Status.SetSuspended(false)
213+
meta.UnsetSuspendedAnnotation(&obj.HelmRelease.Annotations) // use the common annotation unsetter
162214
}
163215
```
164216

165217
### Metrics
166218

167-
Metrics will be reported using the object status.
219+
Custom metrics can be [configured under kube-state-metrics](https://fluxcd.io/flux/monitoring/custom-metrics/#adding-custom-metrics) using the object metadata annotation path.
168220

169221
```
170-
# github.com/fluxcd/kustomize-controller/controller
171-
172-
# imagerepository_controller.go
173-
174-
// Always record suspend, readiness and duration metrics.
175-
// r.Metrics.RecordSuspend(ctx, obj, obj.Spec.Suspend) // no longer used to report suspended
176-
r.Metrics.RecordSuspend(ctx, obj, obj.Status.Suspended)
222+
- name: "resource_info"
223+
help: "The current state of a GitOps Toolkit resource."
224+
each:
225+
type: Info
226+
info:
227+
labelsFromPath:
228+
name: [metadata, name]
229+
labelsFromPath:
230+
...
231+
suspendedAnnotation: [ metadata, annotations, reconcile.fluxcd.io/suspended ]
177232
```
178233

179234
## Implementation History

0 commit comments

Comments
 (0)