May 3, 2022·6 min read·engineeringkubernetescriapi-machineryreleases

Kubernetes 1.24 makes runtime integration an external contract

Dockershim removal completes a runtime boundary that CRI established years earlier. The release also changes how new beta APIs enter a cluster.

Kubernetes 1.24 removes a compatibility adapter that had become an exception to its runtime architecture. Docker Engine remains usable through an external adapter, and Docker-built images remain usable with supported runtimes. What ends is kubelet's responsibility for carrying the Docker-specific translation itself.

That change is central to the May 3 release announcement. Another change applies a similar preference for explicit choices to API availability. Newly introduced beta APIs no longer become available in a cluster by default. Both decisions reduce assumptions that operators could previously inherit without making a separate choice.

I support this direction because Kubernetes' API machinery and reconciliation model are its durable design. Runtime-specific integrations and experimental API adoption need clear ownership around that design. The release makes two of those boundaries more explicit, although the work required differs substantially between them.

Docker images and Docker Engine have different contracts

Dockershim translated kubelet's runtime requests into operations against Docker Engine. Docker Engine does not implement the Container Runtime Interface itself. Removing the adapter means a kubelet on 1.24 needs another supported CRI path, such as containerd or CRI-O, or an external adapter for Docker Engine.

The March readiness guidance already describes cri-dockerd, maintained by Mirantis and Docker, as that external option. Continued use of Docker Engine therefore does not require keeping the adapter in the Kubernetes source tree. It requires installing and maintaining the replacement integration.

An application image is a different contract. The same guidance explains that Docker-built images comply with the OCI format. A developer's use of Docker Desktop does not establish a cluster dependency on the Engine. The image can remain the application artifact while the node uses a different runtime to run it.

This separation limits the necessary application changes without making every cluster migration automatic. Software that expects Docker's socket still depends on Docker's service. A security agent or build workflow can have that dependency even if its own container image is portable. The node runtime and its surrounding integrations must be described independently.

The removal also changes who maintains the exceptional code. A runtime integration inside kubelet competes for attention within Kubernetes development. An external adapter can follow its own maintenance path while speaking the common interface expected by kubelet. Operators who choose that adapter inherit responsibility for its presence in their node configuration.

CRI preserves kubelet's reconciliation responsibility

The 2016 introduction of CRI explains why the interface uses operations on containers and pod sandboxes. The designers considered sending the entire declarative Pod object to the runtime. They rejected that arrangement because each runtime would then need to reproduce significant kubelet behaviour.

Instead, kubelet retains the control logic that drives a workload toward its requested state. RuntimeService exposes operations such as creating a sandbox and starting a container. ImageService handles image operations. The runtime implements execution details while kubelet manages the relationship between those details and the declared Pod.

That division helps new Pod behaviour avoid unnecessary changes to every runtime. It also gives alternative implementations a defined integration point without requiring them to become deeply embedded in kubelet. Dockershim removal follows that design by ending an in-tree special case.

The change is therefore more specific than a general preference for fewer components. Keeping Docker Engine through cri-dockerd still involves an adapter. The architectural benefit is that Kubernetes consumes a standard interface and the Docker-specific translation has an owner outside kubelet. The number of processes alone cannot express that distinction.

For operators, the relevant object of investigation is the runtime endpoint and the software behind it. A Dockerfile in an application repository answers a build question. It says little about whether the kubelet can reach the interface required after this upgrade.

New beta APIs require a deliberate enablement decision

The beta API change has a scope that is easy to overstate. The release announcement says new beta APIs will not be enabled by default. Existing beta APIs remain enabled, and so do new versions of existing beta APIs. It does not announce that every API with a beta version disappears from a default installation.

That qualification preserves continuity for APIs already exposed while changing the default for newly introduced API groups or resources. A cluster no longer necessarily exposes a new beta API merely because the server binary contains its implementation. Availability becomes a separate configuration choice.

This distinction matters to software distributed across clusters with different policies. Code that assumes the presence of a newly introduced beta API needs evidence of enablement. The server's minor version alone cannot establish that the API is accessible. Distribution documentation must describe the required configuration alongside the version requirement.

There is also a difference between an API's maturity label and the default of every related feature. The 1.24 announcement separately says gRPC probes have reached beta and are available by default. This API policy should not be generalized into a claim that all beta functionality is disabled. The release uses different controls for different facilities.

I prefer this explicit approach to new API adoption. A resource type can become a dependency of controllers and stored configuration once users begin relying on it. Requiring an enablement decision makes that dependency easier to associate with a cluster's intended operating policy.

Stable storage changes improve existing workflows

The release is also significant for storage. Storage capacity tracking and volume expansion reach general availability. The announcement describes capacity tracking through CSIStorageCapacity objects, with scheduling improvements for CSI volumes using late binding.

The relevant coordination problem is that placement and storage availability influence one another. A pod can need a volume whose provisioning depends on where the pod will run. Capacity information gives scheduling a way to account for available storage during that decision. It does not turn every storage backend into one interchangeable pool.

Volume expansion concerns a different phase of the lifecycle. It allows resizing an existing persistent volume. General availability establishes the maturity of Kubernetes' support, while the concrete storage implementation still determines the capabilities available for a particular volume.

The release also describes migration of in-tree Azure Disk and OpenStack Cinder integrations to CSI. Its stated goal is to change the internal implementation while preserving the original API. That resembles the runtime work in its separation of integration code, but the compatibility promise and migration mechanism are specific to storage.

These changes matter together because a cluster's declarative objects outlive individual implementations. Moving integration code must preserve the meaning of existing configuration where the project promises compatibility. The runtime change explicitly requires replacing an adapter, while CSI migration aims to retain the original API through the internal transition.

Priority does not have to authorize preemption

Non-preempting PriorityClasses also become stable in 1.24. This is a useful example of separating two decisions that are often treated as one. Priority assigns relative importance to a pod. Preemption determines whether that pod can displace lower-priority work.

The 1.24 PriorityClass type defines preemptionPolicy with Never and PreemptLowerPriority as its choices. If omitted, it defaults to the latter. A class can explicitly retain priority while declining permission to preempt.

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: important-without-preemption
value: 100000
globalDefault: false
preemptionPolicy: Never
description: Important work that must not preempt lower-priority pods.

The example makes the policy choice visible in configuration. It does not create capacity or guarantee immediate placement. It expresses what the scheduler may do when considering lower-priority pods, within the priority mechanism.

That distinction fits a broader operational concern in this release. A preference, a compatibility mechanism, and an authorization to disrupt other work are separate decisions. Clear configuration gives those decisions a place to be reviewed without depending on an implicit association between them.

Adoption needs release-specific runtime information

The announcement includes a warning about CNI-related issues with some containerd releases. It identifies containerd 1.6.0 through 1.6.3 as having pod network setup or teardown problems under particular plugin and configuration conditions. It reports those issues resolved in 1.6.4 and names prepared runtime versions.

This warning reinforces why choosing a runtime family is insufficient. Saying that a cluster uses containerd does not establish that its installed version and network configuration match the release's requirements. Runtime interoperability has concrete versioned implementations beneath the architectural interface.

Kubernetes 1.24 consequently asks for a more explicit description of the node environment. Kubelet needs a supported CRI endpoint. Additional tools may need daemon-specific services. Newly introduced beta APIs require deliberate availability decisions, and storage migrations have their own compatibility scope. These are identifiable operating requirements that can be represented alongside the workloads the cluster reconciles.