|
| 1 | +# Subresources for CustomResources |
| 2 | + |
| 3 | +Authors: @nikhita, @sttts |
| 4 | + |
| 5 | +## Table of Contents |
| 6 | + |
| 7 | +1. [Abstract](#abstract) |
| 8 | +2. [Goals](#goals) |
| 9 | +3. [Non-Goals](#non-goals) |
| 10 | +4. [Proposed Extension of CustomResourceDefinition](#proposed-extension-of-customresourcedefinition) |
| 11 | + 1. [API Types](#api-types) |
| 12 | + 2. [Feature Gate](#feature-gate) |
| 13 | +5. [Semantics](#semantics) |
| 14 | + 1. [Validation Behavior](#validation-behavior) |
| 15 | + 1. [Status](#status) |
| 16 | + 2. [Scale](#scale) |
| 17 | + 2. [Status Behavior](#status-behavior) |
| 18 | + 3. [Scale Behavior](#scale-behavior) |
| 19 | + 1. [Status Replicas Behavior](#status-replicas-behavior) |
| 20 | + 2. [Selector Behavior](#selector-behavior) |
| 21 | +4. [Implementation Plan](#implementation-plan) |
| 22 | +5. [Alternatives](#alternatives) |
| 23 | + 1. [Scope](#scope) |
| 24 | + |
| 25 | +## Abstract |
| 26 | + |
| 27 | +[CustomResourceDefinitions](https://github.com/kubernetes/community/pull/524) (CRDs) were introduced in 1.7. The objects defined by CRDs are called CustomResources (CRs). Currently, we do not provide subresources for CRs. |
| 28 | + |
| 29 | +However, it is one of the [most requested features](https://github.com/kubernetes/kubernetes/issues/38113) and this proposal seeks to add `/status` and `/scale` subresources for CustomResources. |
| 30 | + |
| 31 | +## Goals |
| 32 | + |
| 33 | +1. Support status/spec split for CustomResources: |
| 34 | + 1. Status changes are ignored on the main resource endpoint. |
| 35 | + 2. Support a `/status` subresource HTTP path for status changes. |
| 36 | + 3. `metadata.Generation` is increased only on spec changes. |
| 37 | +2. Support a `/scale` subresource for CustomResources. |
| 38 | +3. Maintain backward compatibility by allowing CRDs to opt-in to enable subresources. |
| 39 | +4. If a CustomResource is already structured using spec/status, allow it to easily transition to use the `/status` and `/scale` endpoint. |
| 40 | +5. Work seamlessly with [JSON Schema validation](https://github.com/kubernetes/community/pull/708). |
| 41 | + |
| 42 | +## Non-Goals |
| 43 | + |
| 44 | +1. Allow defining arbitrary subresources i.e. subresources except `/status` and `/scale`. |
| 45 | +2. Unify the many `Scale` types: as long as there is no generic `Scale` object in Kubernetes, we will propose to introduce yet another `Scale` type in the `apiextensions.k8s.io` api group. If Kubernetes switches to a generic `Scale` object, `apiextensions.k8s.io` will follow. |
| 46 | + |
| 47 | +## Proposed Extension of CustomResourceDefinition |
| 48 | + |
| 49 | +### API Types |
| 50 | + |
| 51 | +The addition of the following types in `apiextensions.k8s.io/v1beta1` is proposed: |
| 52 | + |
| 53 | +```go |
| 54 | +type CustomResourceDefinitionSpec struct { |
| 55 | + Group string |
| 56 | + Version string |
| 57 | + Names CustomResourceDefintion |
| 58 | + Scope ResourceScope |
| 59 | + Validation *CustomResourceValidation |
| 60 | + // SubResources describes the subresources for CustomResources |
| 61 | + // This field is alpha-level and should only be sent to servers that enable |
| 62 | + // subresources via the CurstomResourceSubResources feature gate. |
| 63 | + // +optional |
| 64 | + SubResources *CustomResourceSubResources `json:“subResources,omitempty”` |
| 65 | +} |
| 66 | + |
| 67 | +// CustomResourceSubResources defines the status and scale subresources for CustomResources. |
| 68 | +type CustomResourceSubResources { |
| 69 | + // Status denotes the status subresource for CustomResources |
| 70 | + Status *CustomResourceSubResourceStatus `json:“status,omitempty”` |
| 71 | + // Scale denotes the scale subresource for CustomResources |
| 72 | + Scale *CustomResourceSubResourceScale `json:“scale,omitempty”` |
| 73 | +} |
| 74 | + |
| 75 | +// CustomResourceSubResourceStatus defines how to serve the HTTP path <CR Name>/status. |
| 76 | +type CustomResourceSubResourceStatus struct { |
| 77 | + // The JSON path (e.g. “.status”) of the status of a CustomResource. |
| 78 | + // The whole object is transferred over the wire, but only the status can be mutated via the /status subresource. |
| 79 | + // StatusPath is restricted to be “.status” and defaults to “.status”. |
| 80 | + StatusPath string `json:“statusPath,omitempty”` |
| 81 | + // The JSON path (e.g. “.spec”) of the spec of a CustomResource. |
| 82 | + // SpecPath is restricted to be “.spec” and defaults to “.spec”. |
| 83 | + // Changes to the specified JSON path increase the “.metadata.generation” value. |
| 84 | + SpecPath string `json:“specPath,omitempty”` |
| 85 | +} |
| 86 | + |
| 87 | +// CustomResourceSubResourceScale defines how to serve the HTTP path <CR name>/scale. |
| 88 | +type CustomResourceSubResourceScale struct { |
| 89 | + // required, e.g. “.spec.replicas”. |
| 90 | + // Only JSON paths without the array notation are allowed. |
| 91 | + SpecReplicasPath string `json:“specReplicasPath,omitempty”` |
| 92 | + // optional, e.g. “.status.replicas”. |
| 93 | + // Only JSON paths without the array notation are allowed. |
| 94 | + StatusReplicasPath string `json:“statusReplicasPath,omitempty”` |
| 95 | + // optional, e.g. “.spec.labelSelector”. |
| 96 | + // Only JSON paths without the array notation are allowed. |
| 97 | + LabelSelectorPath string `json:“labelSelectorPath,omitempty”` |
| 98 | +} |
| 99 | + |
| 100 | +// The following is the payload to send over the wire for /scale. It happens |
| 101 | +// to be defined here in apiextensions.k8s.io because we don’t have a global |
| 102 | +// Scale type in meta/v1. Ref: https://github.com/kubernetes/kubernetes/issues/49504 |
| 103 | + |
| 104 | +// Scale represents a scaling request for a resource. |
| 105 | +type Scale struct { |
| 106 | + metav1.TypeMeta `json:",inline"` |
| 107 | + // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. |
| 108 | + // +optional |
| 109 | + metav1.ObjectMeta `json:"metadata,omitempty"` |
| 110 | + |
| 111 | + // defines the behavior of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. |
| 112 | + // +optional |
| 113 | + Spec ScaleSpec `json:"spec,omitempty"` |
| 114 | + |
| 115 | + // current status of the scale. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status. Read-only. |
| 116 | + // +optional |
| 117 | + Status ScaleStatus `json:"status,omitempty"` |
| 118 | +} |
| 119 | + |
| 120 | +// ScaleSpec describes the attributes of a scale subresource. |
| 121 | +type ScaleSpec struct { |
| 122 | + // desired number of instances for the scaled object. |
| 123 | + // +optional |
| 124 | + Replicas int32 `json:"replicas,omitempty"` |
| 125 | +} |
| 126 | + |
| 127 | +// ScaleStatus represents the current status of a scale subresource. |
| 128 | +type ScaleStatus struct { |
| 129 | + // actual number of observed instances of the scaled object. |
| 130 | + Replicas int32 `json:"replicas"` |
| 131 | + |
| 132 | + // label query over pods that should match the replicas count. |
| 133 | + // More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors |
| 134 | + // +optional |
| 135 | + Selector *metav1.LabelSelector `json:"selector"` |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +### Feature Gate |
| 140 | + |
| 141 | +The `SubResources` field in `CustomResourceDefinitionSpec` will be gated under the `CustomResourceSubResources` alpha feature gate. |
| 142 | +If the gate is not open, the value of the new field within `CustomResourceDefinitionSpec` is rejected on creation and updates of CRDs. |
| 143 | + |
| 144 | +## Semantics |
| 145 | + |
| 146 | +### Validation Behavior |
| 147 | + |
| 148 | +#### Status |
| 149 | + |
| 150 | +The status endpoint of a CustomResource receives a full CR object. Changes outside of the status path are ignored. |
| 151 | +For validation everything outside of the JSON Path `StatusPath` can be reset to the existing values of the CustomResource. Then the JSON Schema presented in the CRD is validated against the whole object. |
| 152 | + |
| 153 | +Note: one could extract the status suboject, filter the JSON schema by rules applied to the status and then validate the status subobject only. |
| 154 | +The filter step of the JSON schema though is not obvious in the case that there are status validations not below a properties JSON Schema construct. For that reason, the alternative implementation is preferred as its semantics are much simpler. |
| 155 | + |
| 156 | +#### Scale |
| 157 | + |
| 158 | +Moreover, if the scale subresource is enabled: |
| 159 | + |
| 160 | +- The value at the specified JSON Path `SpecReplicasPath` (e.g. `.spec.replicas`) is validated to ensure that it contains non-negative integer value and is not empty. |
| 161 | + |
| 162 | +- The value at the optional JSON Path `StatusReplicasPath` (e.g. `.status.replicas`) is validated to be a an integer number if it exists (i.e. this can be empty). |
| 163 | + |
| 164 | +- The value at the optional JSON Path `LabelSelectorPath` (e.g. `.spec.labelSelector`) is validated to be a valid label selector if it exists (i.e. this can be empty). |
| 165 | + |
| 166 | +### Status Behavior |
| 167 | + |
| 168 | +If the `/status` subresource is enabled, the following behaviors change: |
| 169 | + |
| 170 | +- The main resource endpoint will ignore all changes in the specified status subpath. |
| 171 | +(note: it will **not** reject requests which try to change the status, following the existing semantics of other resources). |
| 172 | + |
| 173 | +- The `.metadata.generation` field is updated if and only if the value at the specified `SpecPath` (e.g. `.spec`) changes. |
| 174 | + |
| 175 | +- The `/status` subresource receives a full resource object, but only considers the value at the specified `StatusPath` subpath (e.g. `.status`) for the update. |
| 176 | +The value at the `.metadata` subpath is **not** considered for update as decided in https://github.com/kubernetes/kubernetes/issues/45539. |
| 177 | + |
| 178 | +Both the status and the spec (and everything else if there is anything) of the object share the same key in the storage layer, i.e. the value at `.metadata.resourceVersion` is increased for any kind of change. There is no split of status and spec in the storage layer. |
| 179 | + |
| 180 | +### Scale Behavior |
| 181 | + |
| 182 | +The number of CustomResources can be easily scaled up or down depending on the replicas field present in path specified by `SpecPath`. |
| 183 | + |
| 184 | +Only `ScaleSpec.Replicas` can be written. All other values are read-only and changes will be ignored. i.e. upon updating the scale subresource, two fields are modified: |
| 185 | + |
| 186 | +1. The replicas field is copied back from the `Scale` object to the main resource as specified by `SpecReplicasPath` in the CRD, e.g. `.spec.replicas = scale.Spec.Replicas`. |
| 187 | + |
| 188 | +2. The resource version is copied back from the `Scale` object to the main resource before writing to the storage: `.metadata.resourceVersion = scale.ResourceVersion`. |
| 189 | +In other words, the scale and the CustomResource share the resource version used for optimistic concurrency. |
| 190 | +Updates with outdated resource versions are rejected with a conflict error, read requests will return the resource version of the CustomResource. |
| 191 | + |
| 192 | +#### Status Replicas Behavior |
| 193 | + |
| 194 | +As only the `scale.Spec.Replicas` field is to be written to by the CR user, the user-provided controller (not any generic CRD controller) counts its children and then updates the controlled object by writing to the `/status` subresource, i.e. the `scale.Status.Replicas` field is read-only. |
| 195 | + |
| 196 | +#### Selector Behavior |
| 197 | + |
| 198 | +`CustomResourceSubResourceScale.LabelSelectorPath` is the label selector over CustomResources that should match the replicas count. |
| 199 | +The value in the `Scale` object is one-to-one the value from the CustomResource if the label selector is non-empty. |
| 200 | +Intentionally we do not default it to another value from the CustomResource (e.g. `.spec.template.metadata.labels`) as this turned out to cause trouble (e.g. in `kubectl apply`) and it is generally seen as a wrong approach with existing resources. |
| 201 | + |
| 202 | +## Implementation Plan |
| 203 | + |
| 204 | +The `/scale` and `/status` subresources are mostly distinct. It is proposed to do the implementation in two phases (the order does not matter much): |
| 205 | + |
| 206 | +1. `/status` subresource |
| 207 | +2. `/scale` subresource |
| 208 | + |
| 209 | +## Alternatives |
| 210 | + |
| 211 | +### Scope |
| 212 | + |
| 213 | +In this proposal we opted for an opinionated concept of subresources i.e. we restrict the subresource spec to the two very specific subresources: `/status` and `/scale`. |
| 214 | +We do not aim for a more generic subresource concept. In Kubernetes there are a number of other subresources like `/log`, `/exec`, `/bind`. But their semantics is much more special than `/status` and `/scale`. |
| 215 | +Hence, we decided to leave those other subresources to the domain of User provided API Server (UAS) instead of inventing a more complex subresource concept for CustomResourceDefinitions. |
| 216 | + |
| 217 | +**Note**: that the types do not make the addition of other subresources impossible in the future. |
| 218 | + |
| 219 | +We also restrict the JSON path for the status and the spec within the CustomResource. |
| 220 | +We could make them definable by the user and the proposed types actually allow us to open this up in the future. |
| 221 | +For the time being we decided to be opinionated as all status and spec subobjects in existing types live under `.status` and `.spec`. Keeping this pattern imposes consistency on user provided CustomResources as well. |
0 commit comments