Authors: @nikhita, @sttts
- Abstract
- Goals
- Non-Goals
- Proposed Extension of CustomResourceDefinition
- Semantics
- Implementation Plan
- Alternatives
CustomResourceDefinitions (CRDs) were introduced in 1.7. The objects defined by CRDs are called CustomResources (CRs). Currently, we do not provide subresources for CRs.
However, it is one of the most requested features and this proposal seeks to add /status
and /scale
subresources for CustomResources.
- Support status/spec split for CustomResources:
- Status changes are ignored on the main resource endpoint.
- Support a
/status
subresource HTTP path for status changes. metadata.Generation
is increased only on spec changes.
- Support a
/scale
subresource for CustomResources. - Maintain backward compatibility by allowing CRDs to opt-in to enable subresources.
- If a CustomResource is already structured using spec/status, allow it to easily transition to use the
/status
and/scale
endpoint. - Work seamlessly with JSON Schema validation.
- Allow defining arbitrary subresources i.e. subresources except
/status
and/scale
.
The addition of the following external types in apiextensions.k8s.io/v1beta1
is proposed:
type CustomResourceDefinitionSpec struct {
...
// SubResources describes the subresources for CustomResources
// This field is alpha-level and should only be sent to servers that enable
// subresources via the CurstomResourceSubResources feature gate.
// +optional
SubResources *CustomResourceSubResources `json:"subResources,omitempty"`
}
// CustomResourceSubResources defines the status and scale subresources for CustomResources.
type CustomResourceSubResources struct {
// Status denotes the status subresource for CustomResources
Status *CustomResourceSubResourceStatus `json:"status,omitempty"`
// Scale denotes the scale subresource for CustomResources
Scale *CustomResourceSubResourceScale `json:"scale,omitempty"`
}
// CustomResourceSubResourceStatus defines how to serve the HTTP path <CR Name>/status.
type CustomResourceSubResourceStatus struct {
}
// CustomResourceSubResourceScale defines how to serve the HTTP path <CR name>/scale.
type CustomResourceSubResourceScale struct {
// required, e.g. “.spec.replicas”. Must be under `.spec`.
// Only JSON paths without the array notation are allowed.
SpecReplicasPath string `json:"specReplicasPath"`
// optional, e.g. “.status.replicas”. Must be under `.status`.
// Only JSON paths without the array notation are allowed.
StatusReplicasPath string `json:"statusReplicasPath,omitempty"`
// optional, e.g. “.spec.labelSelector”. Must be under `.spec`.
// Only JSON paths without the array notation are allowed.
LabelSelectorPath string `json:"labelSelectorPath,omitempty"`
// ScaleGroupVersion denotes the GroupVersion of the Scale
// object sent as the payload for /scale. It allows transition
// to future versions easily.
// Today only autoscaling/v1 is allowed.
ScaleGroupVersion schema.GroupVersion `json:"groupVersion"`
}
The SubResources
field in CustomResourceDefinitionSpec
will be gated under the CustomResourceSubResources
alpha feature gate.
If the gate is not open, the value of the new field within CustomResourceDefinitionSpec
is dropped on creation and updates of CRDs.
The Scale
object is the payload sent over the wire for /scale
. The polymorphic Scale
type i.e. autoscaling/v1.Scale
is used for the Scale
object.
Since the GroupVersion of the Scale
object is specified in CustomResourceSubResourceScale
, transition to future versions (eg autoscaling/v2.Scale
) can be done easily.
Note: If autoscaling/v1.Scale
is deprecated, then it would be deprecated here as well.
The status endpoint of a CustomResource receives a full CR object. Changes outside of the .status
subpath are ignored.
For validation, the JSON Schema present in the CRD is validated only against the .status
subpath.
To validate only against the schema for the .status
subpath, oneOf
and anyOf
constructs are not allowed within the root of the schema, but only under a properties sub-schema (with this restriction, we can project a schema to a sub-path). The following is forbidden in the CRD spec:
validation:
openAPIV3Schema:
oneOf:
...
Note: The restriction for oneOf
and anyOf
allows us to write a projection function ProjectJSONSchema(schema *JSONSchemaProps, path []string) (*JSONSchemaProps, error)
that can be used to apply a given schema for the whole object to only the sub-path .status
or .spec
.
Moreover, if the scale subresource is enabled:
On update, we copy the values from the Scale
object into the specified paths in the CustomResource, if the path is set (StatusReplicasPath
and LabelSelectorPath
are optional).
If StatusReplicasPath
or LabelSelectorPath
is not set, we validate that the value in Scale
is also not specified and return an error otherwise.
On get
and on update
(after copying the values into the CustomResource as described above), we verify that:
-
The value at the specified JSON Path
SpecReplicasPath
(e.g..spec.replicas
) is a non-negative integer value and is not empty. -
The value at the optional JSON Path
StatusReplicasPath
(e.g..status.replicas
) is an integer value if it exists (i.e. this can be empty). -
The value at the optional JSON Path
LabelSelectorPath
(e.g..spec.labelSelector
) is a valid label selector if it exists (i.e. this can be empty).
Note: The values at the JSON Paths specified by SpecReplicasPath
, LabelSelectorPath
and StatusReplicasPath
are also validated with the same rules when the whole object or, in case the /status
subresource is enabled, the .status
sub-object is updated.
If the /status
subresource is enabled, the following behaviors change:
-
The main resource endpoint will ignore all changes in the status subpath. (note: it will not reject requests which try to change the status, following the existing semantics of other resources).
-
The
.metadata.generation
field is updated if and only if the value at the.spec
subpath changes. Additionally, if the spec does not change,.metadata.generation
is not updated. -
The
/status
subresource receives a full resource object, but only considers the value at the.status
subpath for the update. The value at the.metadata
subpath is not considered for update as decided in kubernetes/kubernetes#45539.
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.
The /status
endpoint supports both get
and update
verbs.
The number of CustomResources can be easily scaled up or down depending on the replicas field present in the .spec
subpath.
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:
-
The replicas field is copied back from the
Scale
object to the main resource as specified bySpecReplicasPath
in the CRD, e.g..spec.replicas = scale.Spec.Replicas
. -
The resource version is copied back from the
Scale
object to the main resource before writing to the storage:.metadata.resourceVersion = scale.ResourceVersion
. In other words, the scale and the CustomResource share the resource version used for optimistic concurrency. Updates with outdated resource versions are rejected with a conflict error, read requests will return the resource version of the CustomResource.
The /scale
endpoint supports both get
and update
verbs.
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.
CustomResourceSubResourceScale.LabelSelectorPath
is the label selector over CustomResources that should match the replicas count.
The value in the Scale
object is one-to-one the value from the CustomResource if the label selector is non-empty.
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.
The /scale
and /status
subresources are mostly distinct. It is proposed to do the implementation in two phases (the order does not matter much):
/status
subresource/scale
subresource
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
.
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
.
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.
Note: The types do not make the addition of other subresources impossible in the future.
We also restrict the JSON path for the status and the spec within the CustomResource.
We could make them definable by the user and the proposed types actually allow us to open this up in the future.
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.