Instance pod never created under Pod Security Admission

Symptom

A Keycloak custom resource is accepted, the StatefulSet is created, and then nothing else happens. No pod ever appears, and the instance never becomes ready.

The StatefulSet's events carry the reason:

create Pod <instance>-0 in StatefulSet <instance> failed error:
pods "<instance>-0" is forbidden: violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false (container "keycloak" must set securityContext.allowPrivilegeEscalation=false),
unrestricted capabilities (container "keycloak" must set securityContext.capabilities.drop=["ALL"]),
runAsNonRoot != true (pod or container "keycloak" must set securityContext.runAsNonRoot=true)

The custom resource's own status may show no error at all: the objects the Operator submitted were accepted, and it is the pod that is refused.

Confirm the namespace is enforcing the profile:

kubectl get ns <namespace> -o jsonpath='{.metadata.labels}'

A pod-security.kubernetes.io/enforce: restricted label is the condition.

On this release, the Operator already handles it

From 26.7.4 the Operator sets a restricted-compliant security context on the pods it generates, so a plain Keycloak resource is admitted in an enforce=restricted namespace with no extra configuration. It sets, at pod level:

  • runAsNonRoot: true
  • seccompProfile.type: RuntimeDefault

and on the container:

  • allowPrivilegeEscalation: false
  • runAsNonRoot: true
  • seccompProfile.type: RuntimeDefault
  • capabilities.drop: ["ALL"]

The same context is applied to the realm-import and update Jobs, so an upgrade and a realm import are admitted too.

runAsUser is deliberately not set: the server image already declares a non-root user, and pinning an id collides with the per-namespace UID range that OpenShift's SCC assigns. readOnlyRootFilesystem is not set either — restricted does not require it.

If you still see the rejection

Each field above is defaulted independently, and an explicit value of yours always wins. So the rejection can only return if something sets one of those fields to a non-compliant value. The place that happens is spec.unsupported.podTemplate:

spec:
  unsupported:
    podTemplate:
      spec:
        securityContext:
          runAsNonRoot: false      # <- your value wins, and it is not compliant

Read what the pod actually got:

kubectl -n <namespace> get pod <pod> \
  -o jsonpath='{.spec.securityContext}{"\n"}{.spec.containers[0].securityContext}{"\n"}'

Remove the offending override, or correct it to a compliant value.

capabilities.drop is defaulted inside any capabilities block you already have, so adding a capability does not silently remove the drop: ["ALL"] the profile requires.

Earlier releases

On releases before 26.7.4 the Operator sets no security context, and a plain resource is rejected. Supply one through spec.unsupported.podTemplate:

spec:
  unsupported:
    podTemplate:
      spec:
        securityContext:
          runAsNonRoot: true
          seccompProfile:
            type: RuntimeDefault
        containers:
          - securityContext:
              allowPrivilegeEscalation: false
              runAsNonRoot: true
              capabilities:
                drop:
                  - ALL
              seccompProfile:
                type: RuntimeDefault

The container entry carries no name: the template is the base the Operator builds on, and it fills in the container's name and image itself.

Alternative: relax the namespace

If the namespace does not have to enforce restricted, label it baseline instead:

kubectl label ns <namespace> pod-security.kubernetes.io/enforce=baseline --overwrite

Use this only where your security policy allows it.


Keycloak™ is a trademark of The Linux Foundation. Alauda is an independent vendor. This product is not affiliated with, endorsed by, or sponsored by The Linux Foundation. All trademarks are the property of their respective owners and are used here for identification purposes only.