@@ -95,9 +95,11 @@ backward-compatibly.
95
95
96
96
Before talking about how to make API changes, it is worthwhile to clarify what
97
97
we mean by API compatibility. Kubernetes considers forwards and backwards
98
- compatibility of its APIs a top priority.
98
+ compatibility of its APIs a top priority. Compatibility is * hard* , especially
99
+ handling issues around rollback-safety. This is something every API change
100
+ must consider.
99
101
100
- An API change is considered forward and backward- compatible if it:
102
+ An API change is considered compatible if it:
101
103
102
104
* adds new functionality that is not required for correct behavior (e.g.,
103
105
does not add a new required field)
@@ -107,24 +109,35 @@ does not add a new required field)
107
109
* which fields are required and which are not
108
110
* mutable fields do not become immutable
109
111
* valid values do not become invalid
112
+ * explicitly invalid values do not become valid
110
113
111
114
Put another way:
112
115
113
- 1 . Any API call (e.g. a structure POSTed to a REST endpoint) that worked before
114
- your change must work the same after your change.
115
- 2 . Any API call that uses your change must not cause problems (e.g. crash or
116
- degrade behavior) when issued against servers that do not include your change.
117
- 3 . It must be possible to round-trip your change (convert to different API
116
+ 1 . Any API call (e.g. a structure POSTed to a REST endpoint) that succeeded
117
+ before your change must succeed after your change.
118
+ 2 . Any API call that does not use your change must behave the same as it did
119
+ before your change.
120
+ 3 . Any API call that uses your change must not cause problems (e.g. crash or
121
+ degrade behavior) when issued against an API servers that do not include your
122
+ change.
123
+ 4 . It must be possible to round-trip your change (convert to different API
118
124
versions and back) with no loss of information.
119
- 4 . Existing clients need not be aware of your change in order for them to
120
- continue to function as they did previously, even when your change is utilized.
125
+ 5 . Existing clients need not be aware of your change in order for them to
126
+ continue to function as they did previously, even when your change is in use.
127
+ 6 . It must be possible to rollback to a previous version of API server that
128
+ does not include your change and have no impact on API objects which do not use
129
+ your change. API objects that use your change will be impacted in case of a
130
+ rollback.
121
131
122
- If your change does not meet these criteria, it is not considered strictly
123
- compatible, and may break older clients, or result in newer clients causing
124
- undefined behavior.
132
+ If your change does not meet these criteria, it is not considered compatible,
133
+ and may break older clients, or result in newer clients causing undefined
134
+ behavior. Such changes are generally disallowed, though exceptions have been
135
+ made in extreme cases (e.g. security or obvious bugs).
125
136
126
- Let's consider some examples. In a hypothetical API (assume we're at version
127
- v6), the ` Frobber ` struct looks something like this:
137
+ Let's consider some examples.
138
+
139
+ In a hypothetical API (assume we're at version v6), the ` Frobber ` struct looks
140
+ something like this:
128
141
129
142
``` go
130
143
// API v6.
@@ -134,7 +147,7 @@ type Frobber struct {
134
147
}
135
148
```
136
149
137
- You want to add a new ` Width ` field. It is generally safe to add new fields
150
+ You want to add a new ` Width ` field. It is generally allowed to add new fields
138
151
without changing the API version, so you can simply change it to:
139
152
140
153
``` go
@@ -146,29 +159,55 @@ type Frobber struct {
146
159
}
147
160
```
148
161
149
- The onus is on you to define a sane default value for ` Width ` such that rule # 1
150
- above is true - API calls and stored objects that used to work must continue to
151
- work.
162
+ The onus is on you to define a sane default value for ` Width ` such that rules
163
+ # 1 and # 2 above are true - API calls and stored objects that used to work must
164
+ continue to work.
152
165
153
166
For your next change you want to allow multiple ` Param ` values. You can not
154
- simply change ` Param string ` to ` Params []string ` (without creating a whole new
155
- API version) - that fails rules #1 and #2 . You can instead do something like:
167
+ simply remove ` Param string ` and add ` Params []string ` (without creating a
168
+ whole new API version) - that fails rules #1 , #2 , #3 , and #6 . Nor can you
169
+ simply add ` Params []string ` and use it instead - that fails #2 and #6 .
170
+
171
+ You must instead define a new field and the relationship between that field and
172
+ the existing field(s). Start by adding the new plural field:
156
173
157
174
``` go
158
- // Still API v6, but kind of clumsy .
175
+ // Still API v6.
159
176
type Frobber struct {
160
177
Height int ` json:"height"`
161
178
Width int ` json:"width"`
162
179
Param string ` json:"param"` // the first param
163
- ExtraParams []string ` json:"extraParams "` // additional params
180
+ Params []string ` json:"params "` // all of the params
164
181
}
165
182
```
166
183
167
- Now you can satisfy the rules: API calls that provide the old style ` Param `
168
- will still work, while servers that don't understand ` ExtraParams ` can ignore
169
- it. This is somewhat unsatisfying as an API, but it is strictly compatible.
170
-
171
- Part of the reason for versioning APIs and for using internal structs that are
184
+ This new field must be inclusive of the singular field. In order to satisfy
185
+ the compatibility rules you must handle all the cases of version skew, multiple
186
+ clients, and rollbacks. This can be handled by defaulting or admission control
187
+ logic linking the fields together with context from the API operation to get as
188
+ close as possible to the user's intentions.
189
+
190
+ Upon any mutating API operation:
191
+ * If only the singular field is specified (e.g. an older client), API logic
192
+ must populate plural[ 0] from the singular value, and de-dup the plural
193
+ field.
194
+ * If only the plural field is specified (e.g. a newer client), API logic must
195
+ populate the singular value from plural[ 0] .
196
+ * If both the singular and plural fields are specified, API logic must
197
+ validate that the singular value matches plural[ 0] .
198
+ * Any other case is an error and must be rejected.
199
+
200
+ For this purpose "is specified" means the following:
201
+ * On a create or patch operation: the field is present in the user-provided input
202
+ * On an update operation: the field is present and has changed from the
203
+ current value
204
+
205
+ Older clients that only know the singular field will continue to succeed and
206
+ produce the same results as before the change. Newer clients can use your
207
+ change without impacting older clients. The API server can be rolled back and
208
+ only objects that use your change will be impacted.
209
+
210
+ Part of the reason for versioning APIs and for using internal types that are
172
211
distinct from any one version is to handle growth like this. The internal
173
212
representation can be implemented as:
174
213
@@ -181,24 +220,26 @@ type Frobber struct {
181
220
}
182
221
```
183
222
184
- The code that converts to/from versioned APIs can decode this into the somewhat
185
- uglier (but compatible!) structures . Eventually, a new API version, let's call
186
- it v7beta1, will be forked and it can use the clean internal structure .
223
+ The code that converts to/from versioned APIs can decode this into the
224
+ compatible structure . Eventually, a new API version, e.g. v7beta1,
225
+ will be forked and it can drop the singular field entirely .
187
226
188
- We've seen how to satisfy rules #1 and #2 . Rule #3 means that you can not
227
+ We've seen how to satisfy rules #1 , # 2 , and #3 . Rule #4 means that you can not
189
228
extend one versioned API without also extending the others. For example, an
190
229
API call might POST an object in API v7beta1 format, which uses the cleaner
191
230
` Params ` field, but the API server might store that object in trusty old v6
192
231
form (since v7beta1 is "beta"). When the user reads the object back in the
193
232
v7beta1 API it would be unacceptable to have lost all but ` Params[0] ` . This
194
233
means that, even though it is ugly, a compatible change must be made to the v6
195
- API.
234
+ API, as above .
196
235
197
- However , this is very challenging to do correctly. It often requires multiple
236
+ For some changes , this can be challenging to do correctly. It may require multiple
198
237
representations of the same information in the same API resource, which need to
199
- be kept in sync in the event that either is changed. For example, let's say you
200
- decide to rename a field within the same API version. In this case, you add
201
- units to ` height ` and ` width ` . You implement this by adding duplicate fields:
238
+ be kept in sync should either be changed.
239
+
240
+ For example, let's say you decide to rename a field within the same API
241
+ version. In this case, you add units to ` height ` and ` width ` . You implement
242
+ this by adding new fields:
202
243
203
244
``` go
204
245
type Frobber struct {
@@ -211,17 +252,17 @@ type Frobber struct {
211
252
212
253
You convert all of the fields to pointers in order to distinguish between unset
213
254
and set to 0, and then set each corresponding field from the other in the
214
- defaulting pass (e.g., ` heightInInches ` from ` height ` , and vice versa), which
215
- runs just prior to conversion. That works fine when the user creates a resource
216
- from a hand-written configuration -- clients can write either field and read
217
- either field, but what about creation or update from the output of GET, or
218
- update via PATCH (see
219
- [ In-place updates] ( https://kubernetes.io/docs/user-guide/managing-deployments/#in-place-updates-of-resources ) )?
220
- In this case , the two fields will conflict, because only one field would be
221
- updated in the case of an old client that was only aware of the old field (e.g.,
222
- ` height ` ).
223
-
224
- Say the client creates:
255
+ defaulting logic (e.g. ` heightInInches ` from ` height ` , and vice versa). That
256
+ works fine when the user creates a sends a hand-written configuration --
257
+ clients can write either field and read either field.
258
+
259
+ But what about creation or update from the output of a GET, or update via PATCH
260
+ (see [ In-place updates] ( https://kubernetes.io/docs/user-guide/managing-deployments/#in-place-updates-of-resources ) )?
261
+ In these cases , the two fields will conflict, because only one field would be
262
+ updated in the case of an old client that was only aware of the old field
263
+ (e.g. ` height ` ).
264
+
265
+ Suppose the client creates:
225
266
226
267
``` json
227
268
{
@@ -252,17 +293,16 @@ then PUTs back:
252
293
}
253
294
```
254
295
255
- The update should not fail, because it would have worked before ` heightInInches `
256
- was added .
296
+ As per the compatibility rules, the update must not fail, because it would have
297
+ worked before the change .
257
298
258
299
## Backward compatibility gotchas
259
300
260
- * A single feature/property cannot be represented using multiple spec fields in the same API version
261
- simultaneously, as the example above shows. Only one field can be populated in any resource at a time, and the client
262
- needs to be able to specify which field they expect to use (typically via API version),
263
- on both mutation and read. Old clients must continue to function properly while only manipulating
264
- the old field. New clients must be able to function properly while only manipulating the new
265
- field.
301
+ * A single feature/property cannot be represented using multiple spec fields
302
+ simultaneously within an API version. Only one representation can be
303
+ populated at a time, and the client needs to be able to specify which field
304
+ they expect to use (typically via API version), on both mutation and read. As
305
+ above, older clients must continue to function properly.
266
306
267
307
* A new representation, even in a new API version, that is more expressive than an
268
308
old one breaks backward compatibility, since clients that only understood the
@@ -283,15 +323,15 @@ was added.
283
323
be set, it is acceptable to add a new option to the union if the [ appropriate
284
324
conventions] ( api-conventions.md#objects ) were followed in the original object.
285
325
Removing an option requires following the [ deprecation process] ( https://kubernetes.io/docs/reference/deprecation-policy/ ) .
286
-
326
+
287
327
* Changing any validation rules always has the potential of breaking some client, since it changes the
288
328
assumptions about part of the API, similar to adding new enum values. Validation rules on spec fields can
289
329
neither be relaxed nor strengthened. Strengthening cannot be permitted because any requests that previously
290
330
worked must continue to work. Weakening validation has the potential to break other consumers and generators
291
331
of the API resource. Status fields whose writers are under our control (e.g., written by non-pluggable
292
332
controllers), may potentially tighten validation, since that would cause a subset of previously valid
293
333
values to be observable by clients.
294
-
334
+
295
335
* Do not add a new API version of an existing resource and make it the preferred version in the same
296
336
release, and do not make it the storage version. The latter is necessary so that a rollback of the
297
337
apiserver doesn't render resources in etcd undecodable after rollback.
@@ -308,16 +348,15 @@ was added.
308
348
309
349
## Incompatible API changes
310
350
311
- There are times when this might be OK, but mostly we want changes that meet this
312
- definition . If you think you need to break compatibility, you should talk to the
313
- Kubernetes team first.
351
+ There are times when incompatible changes might be OK, but mostly we want
352
+ changes that meet the above definitions . If you think you need to break
353
+ compatibility, you should talk to the Kubernetes API reviewers first.
314
354
315
355
Breaking compatibility of a beta or stable API version, such as v1, is
316
356
unacceptable. Compatibility for experimental or alpha APIs is not strictly
317
357
required, but breaking compatibility should not be done lightly, as it disrupts
318
- all users of the feature. Experimental APIs may be removed. Alpha and beta API
319
- versions may be deprecated and eventually removed wholesale, as described in the
320
- [ versioning document] ( ../design-proposals/release/versioning.md ) .
358
+ all users of the feature. Alpha and beta API versions may be deprecated and
359
+ eventually removed wholesale, as described in the [ deprecation policy] ( https://kubernetes.io/docs/reference/deprecation-policy/ ) .
321
360
322
361
If your change is going to be backward incompatible or might be a breaking
323
362
change for API consumers, please send an announcement to
0 commit comments