Declarative API Validation
Kubernetes v1.33 [beta]
Kubernetes 1.35 includes optional declarative validation for APIs. When enabled, the Kubernetes API server can use this mechanism rather than the legacy approach that relies on hand-written Go
code (validation.go files) to ensure that requests against the API are valid.
Kubernetes developers, and people extending the Kubernetes API,
can define validation rules directly alongside the API type definitions (types.go files). Code authors define
special comment tags (e.g., +k8s:minimum=0). A code generator (validation-gen) then uses these tags to produce
optimized Go code for API validation.
While primarily a feature impacting Kubernetes contributors and potentially developers of extension API servers, cluster administrators should understand its behavior, especially during its rollout phases.
Declarative validation tags can apply directly to net-new API fields without requiring any lifecycle mechanism (for example, it is possible to use +k8s:minimum=1). For migrating existing hand-written validations where the declarative validation is shadowing the existing hand-written validation logic, the rollout is controlled by the validation lifecycle tags (+k8s:alpha and +k8s:beta) alongside the DeclarativeValidationBeta feature gate:
DeclarativeValidation: (GA in v1.36, Default:true, LockToDefault:true) The API server runs both the new declarative validation and the old hand-written validation for migrated types/fields in "shadow mode" (Alpha). The results are compared internally.DeclarativeValidationBeta: (Beta, Default:true) Introduced in v1.36. This gate controls the enforcement of beta-stage validation rules. When enabled, rules marked as+k8s:betaare authoritative; when disabled, they revert to shadow mode.DeclarativeValidationTakeover: (Deprecated in v1.36). Previously used to determine whether declarative validation results were authoritative. It is no longer honored but can still be set to prevent "gate not recognized" errors.
Default Behavior (Kubernetes 1.35):
- With
DeclarativeValidationBeta=true(the default), both validation systems run for Alpha and shadowed rules. Beta rules are enforced. - The results of the hand-written validation are used for Alpha rules. The declarative validation runs in a mismatch mode for comparison.
- Mismatches between the two validation systems are logged by the API server and increment the
declarative_validation_mismatch_totalmetric. This data allows contributors to identify and resolve discrepancies during the shadow phase.
Administrators can explicitly disable the DeclarativeValidationBeta feature gate to force +k8s:beta validation rules back into shadow mode if unexpected validation behavior or regressions are observed.
Disabling DeclarativeValidationBeta
As a cluster administrator, you might consider toggling DeclarativeValidationBeta=false under specific circumstances:
- Unexpected Validation Behavior: If enabling
DeclarativeValidationBetaleads to unexpected validation errors or allows objects that were previously invalid. - Performance Regressions: If monitoring indicates significant latency increases (e.g., in
apiserver_request_duration_seconds) correlated with the feature's enablement. - High Mismatch Rate: If the
declarative_validation_mismatch_totalmetric shows frequent mismatches, suggesting potential bugs in the declarative rules affecting the cluster's workloads, even ifDeclarativeValidationBetais false.
To revert +k8s:beta validation rules back to shadow mode, disable the DeclarativeValidationBeta feature gate, for example via command-line arguments: (--feature-gates=DeclarativeValidationBeta=false).
Considerations for downgrade and rollback
Disabling the DeclarativeValidationBeta feature gate acts as a safety mechanism. However, be aware of a potential edge case (considered unlikely due to extensive testing): If a bug in declarative validation (when DeclarativeValidationBeta=true) incorrectly allowed an invalid object to be persisted, disabling the feature gate might then cause subsequent updates to that specific object to be blocked by the now-authoritative (and correct) hand-written validation. Resolving this might require manual correction of the stored object, potentially via direct etcd modification in rare cases.
For details on managing feature gates, see Feature Gates.
Declarative validation tag reference
This document provides a comprehensive reference for all available declarative validation tags.
Tag catalog
| Tag | Description | Stability |
|---|---|---|
+k8s:alpha |
Puts a validation tag in Shadow mode (Metrics only). | Stable |
+k8s:beta |
Puts a validation tag in Enforced mode (disableable via DeclarativeValidationBeta). |
Stable |
+k8s:eachKey |
Declares a validation for each key in a map. | Alpha |
+k8s:eachVal |
Declares a validation for each value in a map or list. | Alpha |
+k8s:enum |
Indicates that a string type is an enum. | Beta |
+k8s:forbidden |
Indicates that a field may not be specified. | Alpha |
+k8s:format |
Indicates that a string field has a particular format. | Stable |
+k8s:ifDisabled |
Declares a validation that only applies when an option is disabled. | Alpha |
+k8s:ifEnabled |
Declares a validation that only applies when an option is enabled. | Alpha |
+k8s:isSubresource |
Specifies that validations in a package only apply to a specific subresource. | Stable |
+k8s:item |
Declares a validation for an item of a slice declared as a +k8s:listType=map. |
Stable |
+k8s:listMapKey |
Declares a named sub-field of a list's value-type to be part of the list-map key. | Stable |
+k8s:listType |
Declares a list field's semantic type. | Stable |
+k8s:maxItems |
Indicates that a list field has a limit on its size. | Stable |
+k8s:maxLength |
Indicates that a string field has a limit on its length. | Stable |
+k8s:minimum |
Indicates that a numeric field has a minimum value. | Stable |
+k8s:neq |
Verifies the field's value is not equal to a specific disallowed value. | Alpha |
+k8s:opaqueType |
Indicates that any validations declared on the referenced type will be ignored. | Alpha |
+k8s:optional |
Indicates that a field is optional to clients. | Stable |
+k8s:required |
Indicates that a field must be specified by clients. | Stable |
+k8s:subfield |
Declares a validation for a subfield of a struct. | Stable |
+k8s:supportsSubresource |
Declares a supported subresource for the types within a package. | Stable |
+k8s:unionDiscriminator |
Indicates that this field is the discriminator for a union. | Stable |
+k8s:unionMember |
Indicates that this field is a member of a union group. | Stable |
+k8s:zeroOrOneOfMember |
Indicates that this field is a member of a zero-or-one-of group. | Stable |
Tag Reference
+k8s:alpha
Description:
The +k8s:alpha tag enables shadow mode for a validation rule. It represents the first phase of the validation lifecycle for safely migrating existing hand-written validation logic to declarative tags. Do not use this tag for net-new API fields, which should apply declarative validation tags directly.
When a validation is shadowed with +k8s:alpha, the validation logic executed includes the original hand-written
validation logic as it normally would but additionally runs the shadowed declarative validation in a non-blocking way,
and then verifies the results are matching.
Any mismatches or panics are recorded via metrics (for example: declarative_validation_mismatch_total and declarative_validation_panic_total).
This shadow mechanism enables contributors and administrators to evaluate declarative validation rules in a live environment without affecting cluster behavior. By monitoring the mismatch metrics, you can verify that the declarative rules behave identically to the existing hand-written logic before promoting the validation to beta.
Stability Level: Stable
Arguments:
since(string, required): The Kubernetes version in which the validation was first shadowed.
Payload:
<validation-tag>: The standard declarative validation tag to be shadowed (e.g.,+k8s:minimum=1).
Usage Example:
type MyStruct struct {
// +k8s:alpha(since:"1.36")=+k8s:minimum=1
MyField int `json:"myField"`
}
+k8s:beta
Description:
The +k8s:beta tag enables enforced mode for validation rules migrating from hand-written logic, controlled by the DeclarativeValidationBeta feature gate. Do not use this tag for net-new API fields, which should apply declarative validation tags directly.
After a validation rule has been evaluated in shadow mode (via +k8s:alpha), it is promoted to beta. When DeclarativeValidationBeta is enabled (the default), +k8s:beta rules are enforced and authoritative. Disabling the feature gate reverts +k8s:beta rules to shadow mode, providing a rollback mechanism if regressions occur.
Stability Level: Stable
Arguments:
since(string, required): The Kubernetes version in which the validation was promoted to beta.
Payload:
<validation-tag>: The standard declarative validation tag to be enforced (e.g.,+k8s:minimum=1).
Usage Example:
type MyStruct struct {
// +k8s:beta(since:"1.37")=+k8s:minimum=1
MyField int `json:"myField"`
}
+k8s:eachKey
Description:
Declares a validation for each key in a map.
Stability Level: Alpha
Payload:
<validation-tag>: The tag to evaluate for each key.
Usage Example:
type MyStruct struct {
// +k8s:eachKey=+k8s:minimum=1
MyMap map[int]string `json:"myMap"`
}
In this example, eachKey is used to specify that the +k8s:minimum tag should be applied to each int key in MyMap. This means that all keys in the map must be >= 1.
+k8s:eachVal
Description:
Declares a validation for each value in a map or list.
Stability Level: Alpha
Payload:
<validation-tag>: The tag to evaluate for each value.
Usage Example:
type MyStruct struct {
// +k8s:eachVal=+k8s:minimum=1
MyMap map[string]int `json:"myMap"`
}
In this example, eachVal is used to specify that the +k8s:minimum tag should be applied to each element in MyList. This means that all fields in MyStruct must be >= 1.
+k8s:enum
Description:
Indicates that a string type is an enum. All const values of this type are considered values in the enum.
Stability Level: Beta
Usage Example:
First, define a new string type and some constants of that type:
// +k8s:enum
type MyEnum string
const (
MyEnumA MyEnum = "A"
MyEnumB MyEnum = "B"
)
Then, use this type in another struct:
type MyStruct struct {
MyField MyEnum `json:"myField"`
}
The validation logic will ensure that MyField is one of the defined enum values ("A" or "B").
+k8s:forbidden
Description:
Indicates that a field may not be specified.
Stability Level: Alpha
Usage Example:
type MyStruct struct {
// +k8s:forbidden
MyField string `json:"myField"`
}
In this example, MyField cannot be provided (it is forbidden) when creating or updating MyStruct.
+k8s:format
Description:
Indicates that a string field has a particular format.
Stability Level: Stable
Payloads:
k8s-ip: This field holds an IPv4 or IPv6 address value. IPv4 octets may have leading zeros.k8s-long-name: This field holds a Kubernetes "long name", aka a "DNS subdomain" value.k8s-short-name: This field holds a Kubernetes "short name", aka a "DNS label" value.
Usage Example:
type MyStruct struct {
// +k8s:format=k8s-ip
IPAddress string `json:"ipAddress"`
// +k8s:format=k8s-long-name
Subdomain string `json:"subdomain"`
// +k8s:format=k8s-short-name
Label string `json:"label"`
}
In this example:
IPAddressmust be a valid IP address.Subdomainmust be a valid DNS subdomain.Labelmust be a valid DNS label.
+k8s:ifDisabled
Description:
Declares a validation that only applies when an option is disabled.
Stability Level: Alpha
Arguments:
<option>(string, required): The name of the option.
Payload:
<validation-tag>: This validation tag will be evaluated only if the validation option is disabled.
Usage Example:
type MyStruct struct {
// +k8s:ifDisabled("my-feature")=+k8s:required
MyField string `json:"myField"`
}
In this example, MyField is required only if the "my-feature" option is disabled.
+k8s:ifEnabled
Description:
Declares a validation that only applies when an option is enabled.
Stability Level: Alpha
Arguments:
<option>(string, required): The name of the option.
Payload:
<validation-tag>: This validation tag will be evaluated only if the validation option is enabled.
Usage Example:
type MyStruct struct {
// +k8s:ifEnabled("my-feature")=+k8s:required
MyField string `json:"myField"`
}
In this example, MyField is required only if the "my-feature" option is enabled.
+k8s:isSubresource
Stability Level: Stable
Description:
The +k8s:isSubresource tag is a package-level comment that scopes the validation rules within that package to a specific subresource. It essentially tells the code generator, "The validation logic defined here is the specific implementation for this subresource and should not be applied to the root object or any other subresource."
CRITICAL DEPENDENCY:
This tag is dependent on a corresponding +k8s:supportsSubresource tag being present in the package where the main API type is defined.
+k8s:supportsSubresourceopens the door by telling the dispatcher that a subresource is valid.+k8s:isSubresourceprovides the specialized validation logic that runs when a request comes through that door.
If you use +k8s:isSubresource without the corresponding +k8s:supportsSubresource declaration on the main type, the specialized validation code will be generated but will be unreachable. The main dispatcher will not recognize the subresource path and will reject the request before it can be routed to your specific validation logic.
This dependency allows for powerful organization, such as placing your main API types in one package and defining their subresource-specific validations in separate, dedicated packages.
Scope: Package
Payload:
<subresource-path>: The path of the subresource to which the validations in this package should apply (e.g.,"/status","/scale").
Usage Example:
This two-part example demonstrates the intended use case of separating concerns.
1. Declare Support in the Main API Package:
First, declare that the Deployment type supports /scale validation in its primary package.
File: staging/src/k8s.io/api/apps/v1/doc.go
// This enables the validation dispatcher to handle requests for "/scale".
// +k8s:supportsSubresource="/scale"
package v1
// ... includes the definition for the Deployment type
2. Scope Validation Logic in a Separate Package:
Next, create a separate package for the validation rules that are specific only to the /scale subresource.
File: staging/src/k8s.io/api/apps/v1/validations/scale/doc.go
// This ensures the rules in this package ONLY run for the "/scale" subresource.
// +k8s:isSubresource="/scale"
package scale
import "k8s.io/api/apps/v1"
// Validation code in this package would reference types from package v1 (e.g., v1.Scale).
// The generated validation function will only be invoked for requests to the "/scale"
// subresource of a type defined in a package that supports it.
+k8s:item
Description:
Declares a validation for an item of a slice declared as a +k8s:listType=map. The item to match is declared by providing field-value pair arguments where the field is a listMapKey. All listMapKey key fields must be specified.
Stability Level: Stable
Usage:
+k8s:item(<listMapKey-JSON-field-name>: <value>,...)=<validation-tag>
+k8s:item(stringKey: "value", intKey: 42, boolKey: true)=<validation-tag>
Arguments must be named with the JSON names of the list-map key fields. Values can be strings, integers, or booleans.
Payload:
<validation-tag>: The tag to evaluate for the matching list item.
Usage Example:
type MyStruct struct {
// +k8s:listType=map
// +k8s:listMapKey=type
// +k8s:item(type: "Approved")=+k8s:zeroOrOneOfMember
// +k8s:item(type: "Denied")=+k8s:zeroOrOneOfMember
MyConditions []MyCondition `json:"conditions"`
}
type MyCondition struct {
Type string `json:"type"`
Status string `json:"status"`
}
In this example:
- The condition with
type"Approved" is part of a zero-or-one-of group. - The condition with
type"Denied" is part of a zero-or-one-of group.
+k8s:listMapKey
Description:
Declares a named sub-field of a list's value-type to be part of the list-map key. This tag is required when +k8s:listType=map is used. Multiple +k8s:listMapKey tags can be used on a list-map to specify that it is keyed off of multiple fields.
Stability Level: Stable
Payload:
<field-json-name>: The JSON name of the field to be used as the key.
Usage Example:
// +k8s:listType=map
// +k8s:listMapKey=keyFieldOne
// +k8s:listMapKey=keyFieldTwo
type MyList []MyStruct
type MyStruct struct {
keyFieldOne string `json:"keyFieldOne"`
keyFieldTwo string `json:"keyFieldTwo"`
valueField string `json:"valueField"`
}
In this example, listMapKey is used to specify that the keyField of MyStruct should be used as the key for the list-map.
+k8s:listType
Description:
Declares a list field's semantic type. This tag is used to specify how a list should be treated, for example, as a map or a set.
Stability Level: Stable
Payload:
atomic: The list is treated as a single atomic value.map: The list is treated as a map, where each element has a unique key. Requires the use of+k8s:listMapKey.set: The list is treated as a set, where each element is unique.
Usage Example:
// +k8s:listType=map
// +k8s:listMapKey=keyField
type MyList []MyStruct
type MyStruct struct {
keyField string `json:"keyField"`
valueField string `json:"valueField"`
}
In this example, MyList is declared as a list of type map, with keyField as the key. This means that the validation logic will ensure that each element in the list has a unique keyField.
+k8s:maxItems
Description:
Indicates that a list field has a limit on its size.
Stability Level: Stable
Payload:
<non-negative integer>: This field must be no more than X items long.
Usage Example:
type MyStruct struct {
// +k8s:maxItems=5
MyList []string `json:"myList"`
}
In this example, MyList cannot contain more than 5 items.
+k8s:maxLength
Description:
Indicates that a string field has a limit on its length.
Stability Level: Stable
Payload:
<non-negative integer>: This field must be no more than X characters long.
Usage Example:
type MyStruct struct {
// +k8s:maxLength=10
MyString string `json:"myString"`
}
In this example, MyString cannot be longer than 10 characters.
+k8s:minimum
Description:
Indicates that a numeric field has a minimum value.
Stability Level: Stable
Payload:
<integer>: This field must be greater than or equal to x.
Usage Example:
type MyStruct struct {
// +k8s:minimum=0
MyInt int `json:"myInt"`
}
In this example, MyInt must be greater than or equal to 0.
+k8s:neq
Description:
Verifies the field's value is not equal to a specific disallowed value. Supports string, integer, and boolean types.
Stability Level: Alpha
Payload:
<value>: The disallowed value. The parser will infer the type (string, int, bool).
Usage Example:
type MyStruct struct {
// +k8s:neq="disallowed"
MyString string `json:"myString"`
// +k8s:neq=0
MyInt int `json:"myInt"`
// +k8s:neq=true
MyBool bool `json:"myBool"`
}
In this example:
MyStringcannot be equal to"disallowed".MyIntcannot be equal to0.MyBoolcannot be equal totrue.
+k8s:opaqueType
Description:
Indicates that any validations declared on the referenced type will be ignored. If a referenced type's package is not included in the generator's current flags, this tag must be set, or code generation will fail (preventing silent mistakes). If the validations should not be ignored, add the type's package to the generator using the --readonly-pkg flag.
Stability Level: Alpha
Usage Example:
import "some/external/package"
type MyStruct struct {
// +k8s:opaqueType
ExternalField package.ExternalType `json:"externalField"`
}
In this example, any validation tags on package.ExternalType will be ignored.
+k8s:optional
Description:
Indicates that a field is optional to clients.
Stability Level: Stable
Usage Example:
type MyStruct struct {
// +k8s:optional
MyField string `json:"myField"`
}
In this example, MyField is not required to be provided when creating or updating MyStruct.
+k8s:required
Description:
Indicates that a field must be specified by clients.
Stability Level: Stable
Usage Example:
type MyStruct struct {
// +k8s:required
MyField string `json:"myField"`
}
In this example, MyField must be provided when creating or updating MyStruct.
+k8s:subfield
Description:
Declares a validation for a subfield of a struct.
Stability Level: Beta
Arguments:
<field-json-name>(string, required): The JSON name of the subfield.
Payload:
<validation-tag>: The tag to evaluate for the subfield.
Usage Example:
type MyStruct struct {
// +k8s:subfield("mySubfield")=+k8s:required
MyStruct MyStruct `json:"MyStruct"`
}
type MyStruct struct {
MySubfield string `json:"mySubfield"`
}
In this example, MySubfield within MyStruct is required.
+k8s:supportsSubresource
Stability Level: Stable
Description:
The +k8s:supportsSubresource tag is a package-level comment tag that declares which subresources are valid targets for validation for the types within that package. Think of this tag as registering an endpoint; it tells the validation framework that a specific subresource path is recognized and should not be immediately rejected.
When the validation code is generated, this tag adds the specified subresource path to the main dispatch function for a type. This allows incoming requests for that subresource to be routed to a validation implementation.
Multiple tags can be used to declare support for several subresources. If no +k8s:supportsSubresource tags are present in a package, validation is only enabled for the root resource (e.g., .../myresources/myobject), and any requests to subresources will fail with a "no validation found" error.
Standalone Usage:
If you use +k8s:supportsSubresource without a corresponding +k8s:isSubresource tag for a specific validation, the validation rules for the root object will be applied to the subresource by default.
Scope: Package
Payload:
<subresource-path>: The path of the subresource to support (e.g.,"/status","/scale").
Usage Example:
By adding these tags, you are enabling the validation system to handle requests for the /status and /scale subresources for the types defined in package v1.
File: staging/src/k8s.io/api/core/v1/doc.go
// +k8s:supportsSubresource="/status"
// +k8s:supportsSubresource="/scale"
package v1
+k8s:unionDiscriminator
Description:
Indicates that this field is the discriminator for a union.
Stability Level: Stable
Arguments:
union(string, optional): The name of the union, if more than one exists.
Usage Example:
type MyStruct struct {
TypeMeta int
// +k8s:unionDiscriminator
D D `json:"d"`
// +k8s:unionMember
// +k8s:optional
M1 *M1 `json:"m1"`
// +k8s:unionMember
// +k8s:optional
M2 *M2 `json:"m2"`
}
type D string
const (
DM1 D = "M1"
DM2 D = "M2"
)
type M1 struct{}
type M2 struct{}
In this example, the Type field is the discriminator for the union. The value of Type will determine which of the union members (M1 or M2) is expected to be present.
+k8s:unionMember
Description:
Indicates that this field is a member of a union.
Stability Level: Stable
Arguments:
union(string, optional): The name of the union, if more than one exists.memberName(string, optional): The discriminator value for this member. Defaults to the field's name.
Usage Example:
type MyStruct struct {
// +k8s:unionMember(union: "union1")
// +k8s:optional
M1 *M1 `json:"u1m1"`
// +k8s:unionMember(union: "union1")
// +k8s:optional
M2 *M2 `json:"u1m2"`
}
type M1 struct{}
type M2 struct{}
In this example, M1 and M2 are members of the named union union1.
+k8s:zeroOrOneOfMember
Description:
Indicates that this field is a member of a zero-or-one-of union. A zero-or-one-of union allows at most one member to be set. Unlike regular unions, having no members set is valid.
Stability Level: Stable
Arguments:
union(string, optional): The name of the union, if more than one exists.memberName(string, optional): The custom member name for this member. Defaults to the field's name.
Usage Example:
type MyStruct struct {
// +k8s:zeroOrOneOfMember
// +k8s:optional
M1 *M1 `json:"m1"`
// +k8s:zeroOrOneOfMember
// +k8s:optional
M2 *M2 `json:"m2"`
}
type M1 struct{}
type M2 struct{}
In this example, at most one of A or B can be set. It is also valid for neither to be set.