Skip to content

Aggregated APIService's /openapi/v2 gets corrupted (ArbitraryKeys/SchemaProps/SwaggerSchemaProps leak as literal schema properties) when the APIService's kube-openapi is newer than the parent apiserver's #1868

Description

@andrewegel

FWD: Analysis generated by Claude Sonnet 5, then edited / confirmed by a human (me)

What happened:

After upgrading metrics-server from v0.8.1 to v0.9.0 on an EKS 1.35 cluster, the cluster's merged /openapi/v2 document became corrupt. The apiserver logs (CloudWatch, kube-apiserver-* stream) show:

E0901 02:01:25.001 handler.go:160] Error in OpenAPI handler:
  ...resource.Quantity_v2 has invalid properties: ArbitraryKeys, SchemaProps, SwaggerSchemaProps
  ...meta.v1.APIGroupList_v2 / APIGroup_v2 / APIResourceList...

and equivalent errors for meta.v1.APIGroupList_v2, meta.v1.APIGroup_v2, meta.v1.APIResourceList_v2 - To reproduce:

$ curl -sk -o /dev/null -w "protobuf -> HTTP %{http_code}\n" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Accept: application/com.github.proto-openapi.spec.v2@v1.0+protobuf" \
    https://<cluster-endpoint>/openapi/v2
protobuf -> HTTP 503

$ curl -sk -o /dev/null -w "json     -> HTTP %{http_code}\n" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Accept: application/json" \
    https://<cluster-endpoint>/openapi/v2
json     -> HTTP 200

The 503 effectively DDOS's the KubeAPI server for everyone else using this KubeAPI server, leading to:

Error from server (ServiceUnavailable): the server is currently unable to handle the request

From other Clients.

metrics-server v0.9.0 bumped k8s.io/apiserver from v0.33.7 → v0.36.2, which transitively bumped k8s.io/kube-openapi from v0.0.0-20250318190949-c8a335a9a2ff (v0.8.1) to v0.0.0-20260520065146-aa012df4f4af (v0.9.0).

Within that range, commit add4291a9eec163878911f65bbb7534d08b05d31 ("Migrate callers to post-split go-json-experiment API", merged in #604) changed Schema.MarshalJSONTo in pkg/validation/spec/schema.go:

 func (s Schema) MarshalJSONTo(enc *jsontext.Encoder) error {
      type ArbitraryKeys map[string]interface{}
      var x struct {
-             ArbitraryKeys
+             ArbitraryKeys      ArbitraryKeys              `json:",inline"`
              SchemaProps        schemaPropsOmitZero        `json:",inline"`
              SwaggerSchemaProps swaggerSchemaPropsOmitZero `json:",inline"`
              Schema             string                     `json:"$schema,omitempty"`

Before this change, ArbitraryKeys was an anonymous/embedded field, implicitly flattened by the pre-split experimental JSON v2 API (MarshalNextJSON). After the change, it's a named field that depends on the new jsontext/post-split API correctly honoring the explicit json:",inline" tag to flatten it (along with SchemaProps and SwaggerSchemaProps) into the parent object.

The field names ArbitraryKeys, SchemaProps, SwaggerSchemaProps are leaking into the served schema as literal JSON properties instead of being inlined.

What you expected to happen:

Upgrading an aggregated APIService's implementation (metrics-server) to a newer line of its Kubernetes library dependencies shouldn't corrupt OpenAPI aggregation on a parent apiserver that is still within the normal supported version window.

Anything else we need to know?:

Not yet experimented: whether the malformed document is produced by metrics-server itself when serving its own spec, or by the parent apiserver's aggregator when merging it; and whether this requires a specific Go toolchain/build flag (e.g. GOEXPERIMENT=jsonv2) to route through MarshalJSONTo at all, versus the MarshalJSON path.

Environment:

  • Parent apiserver: EKS v1.35.6-eks-bca9cf6

  • Aggregated APIService: metrics-server, image 0.9.0 (previously 0.8.1, working)

  • Metrics Server manifest

spoiler for Metrics Server manifest:
  • Kubelet config:
spoiler for Kubelet config:
  • Metrics server logs:
spoiler for Metrics Server logs:
  • Status of Metrics API:
spolier for Status of Metrics API:
kubectl describe apiservice v1beta1.metrics.k8s.io

/kind bug

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/bugCategorizes issue or PR as related to a bug.needs-triageIndicates an issue or PR lacks a `triage/foo` label and requires one.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions