April 8, 2021·6 min read·engineeringkubernetescronjobssecurity

Kubernetes 1.21 stabilizes CronJobs while reconsidering Pod security

Kubernetes 1.21 promotes established workload APIs while deprecating PodSecurityPolicy. The difference reflects how API maturity depends on usable operational contracts as well as implementation age.

PodSecurityPolicy has been in Kubernetes long enough to appear permanent, but its application rules remain difficult to inspect and its rollout remains difficult to make safe. Kubernetes 1.21 deprecates it while promoting CronJobs to stable. These decisions concern different subsystems, yet together they explain a useful distinction between an old API and an API whose operating model the project can maintain.

The 1.21 announcement reports 51 enhancements, including 13 graduating to stable. CronJobs have been beta since Kubernetes 1.8. PodSecurityPolicy also remains functional in this release, but the project now plans its removal in 1.25. Deprecation starts that process without immediately removing the protection from clusters that use it.

I put considerable value on Kubernetes' API machinery. It makes desired state accessible to controllers and gives operators a common way to describe systems. That machinery is most useful when an API's permissions and state transitions are understandable, which is precisely where the PodSecurityPolicy explanation identifies problems.

Why Pod creation needs another decision

RBAC answers whether a caller may perform an API operation on a resource. It does not decide whether every value inside that resource is acceptable. Permission to create a Pod therefore needs a separate constraint if a cluster wants to prohibit security-sensitive configurations.

The PodSecurityPolicy deprecation explanation, published on 6 April, describes that distinction directly. A Pod can describe an ordinary application or request privileges that expose its underlying node. Both requests involve creating the same resource type, so object-level permission alone cannot express the intended restriction.

PodSecurityPolicy supplies admission control for those fields. Administrators create policy objects describing permitted settings and use RBAC rules to control access to those policies. Admission evaluates whether a Pod meets an applicable policy. Some policy fields also provide defaults by modifying the Pod.

That last behavior means PSP participates in both validation and mutation. A policy can reject a requested setting, while another part of its behavior can fill in a setting the caller omitted. Understanding the admitted object therefore requires knowing which policy applied and which defaults that policy can supply.

PodSecurityContext has a different role. It is part of the Pod specification and describes security settings that the kubelet and runtime use to run the workload. PSP constrains or defaults some of those values. Deprecating PSP does not deprecate the security context or remove its fields.

Why the existing design is being replaced

The deprecation post identifies usability problems that cannot be repaired without breaking changes. Policy selection is confusing, broader permissions are easy to grant accidentally, and inspecting the applicable policies is difficult. Defaulting only works for some settings, which makes its behavior harder to predict.

PSP also lacks an audit or dry-run mode. That omission has a direct effect on adoption. An administrator trying to introduce policy into an existing cluster cannot first observe all the rejections that enforcement would produce through a built-in PSP reporting mode. The project identifies that limitation as an obstacle to safe retrofitting and to enabling PSP by default.

The planned replacement has the working title PSP Replacement Policy. As of this release, it has a proposal and prototype, with an alpha release targeted for Kubernetes 1.22. It is a plan under development, so clusters cannot treat it as a completed replacement included in 1.21.

Its intended scope is deliberately smaller. External admission controllers already cover more elaborate policy requirements. The proposed built-in mechanism concentrates on straightforward deployment and adoption, including gradual rollout behavior. It also needs to coexist with external admission controllers when a cluster needs more flexibility.

This creates a practical distinction between simple and complex PSP installations. The deprecation explanation expects a small set of policies with straightforward namespace and service-account relationships to fit the proposed replacement more readily. Numerous policies with intricate bindings may require an external controller. Preserving every PSP configuration is not the stated design goal.

My preference is for a built-in security mechanism whose enforcement can be inspected before it blocks existing workloads. That judgement follows from the operational contract described here. A policy system that is difficult to introduce safely can leave clusters without the restriction it was intended to provide.

CronJobs acquire a stable API

CronJobs address a more bounded problem. They describe recurring work and the Job template to instantiate when a schedule becomes due. With 1.21, the stable API is batch/v1, and the release's versioned API definitions describe the relevant controls.

A CronJob specifies a cron-format schedule. Its concurrency policy determines what happens when another execution is due while a previous one is still active. Allow permits concurrency and is the default. Forbid skips the next run when the previous one has not finished. Replace cancels the active Job and substitutes a new one.

A short example makes that choice explicit:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: periodic-check
spec:
  schedule: "*/15 * * * *"
  concurrencyPolicy: Forbid
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: Never
          containers:
            - name: check
              image: busybox:1.32
              command: ["sh", "-c", "date"]

This configuration requests a scheduled Job every fifteen minutes and disallows concurrent runs of this CronJob. The command is intentionally small because the relevant feature is the controller's scheduling policy. A longer-running task can cause scheduled runs to be skipped under Forbid, so that field is part of the application's execution semantics.

The API also provides startingDeadlineSeconds for a missed scheduled start. Its suspend field stops subsequent executions without changing already started executions. History limits control how many finished successful and failed Jobs are retained. Those are separate decisions, so suspension should not be interpreted as cancellation of work already running.

The distinction between suspension and concurrency policy is useful during maintenance. Suspending a CronJob prevents future starts, while the overlap policy governs what happens when an unsuspended schedule becomes due. Neither field describes a new application-level transaction or rollback mechanism.

Stable status gives this established workload a stable API version. It does not choose the appropriate overlap policy for an application. A task whose repeated executions can safely overlap has a different requirement from a task that must skip an invocation while the preceding Job remains active.

Configuration can become an immutable input

Immutable Secrets and ConfigMaps also graduate to stable in 1.21. Setting the new immutability field prevents changes to the object's data. Updating that configuration then requires a new object with a unique name and new Pods that consume it, according to the release announcement.

The operational consequence is a change in how configuration reaches a workload. A mutable object permits consumers capable of observing changes to receive an update under the same object name. An immutable object makes a new configuration an explicitly different input. A deployment needs to refer to that new input.

The release also identifies a scaling benefit. Components do not need to maintain watches for changes to an immutable object's contents. Preventing mutation therefore reduces some configuration-observation work as well as preventing an accidental in-place update.

Immutability does not decide whether a proposed configuration is correct. It changes the lifecycle of an accepted configuration and the mechanism needed to replace it. That can make a rollout easier to reason about because the old and new configurations have distinct identities.

Node shutdown becomes part of workload termination

Graceful node shutdown reaches beta in this release. The kubelet uses systemd's notification of impending shutdown to begin terminating Pods through their expected lifecycle. Without that coordination, the machine can shut down without the workload receiving the graceful termination behavior it expects.

This feature extends reconciliation to an event outside the Kubernetes API. The operating system knows that the machine is about to stop, and the kubelet needs to translate that knowledge into workload termination. It cannot provide useful shutdown coordination after the operating system has already removed the opportunity to run it.

EndpointSlice and PodDisruptionBudget also graduate to stable in 1.21, while dual-stack networking moves to beta and becomes enabled by default. These maturity changes concern separate operational contracts and should be evaluated individually. Stable CronJobs, immutable configuration, and shutdown coordination each make an established behavior more explicit, while PSP deprecation acknowledges that the existing policy model needs a different design.

For clusters already using PSP, the immediate behavior remains available. The substantive work is identifying which constraints must survive its eventual removal and whether they fit the proposed built-in replacement or require an external admission controller. Kubernetes 1.21 provides time for that decision while leaving the current enforcement mechanism functional.