The manifest at the centre of this post cannot be pasted at all — not because of its API version, which is the smaller problem, but because Blogger's escaping and a line-number gutter are inside the code fence — and once you have retyped it the API server rejects it on two fields that did not exist in 2018, defaults four more against it, tells you nothing about the one rule shape that has silently stopped matching, and would have you register a webhook the project's own guidance now says to reach for last¶
Post — Extensible Admission is Beta,
2018-01-11, Kubernetes v1.9. Part of the Five Days of Kubernetes 1.9 series whose index post the
census marks skip. It carries no author frontmatter; the credit is a sentence in its last
paragraph — "this work, driven by both Red Hat and Google" — and the reviewer list on the pinned
page that replaced it is where you can still read who owned this area.
As written — the post is an explainer, not a walkthrough, and it has exactly one artefact you are meant to copy. Everything before that artefact exists to make it legible.
It sets the stage first. "The admission stage of API server processing is one of the most powerful tools for securing a Kubernetes cluster by restricting the objects that can be created, but it has always been limited to compiled code. In 1.9, we promoted webhooks for admission to beta, allowing you to leverage admission from outside the API server process."
Then the two-phase model, which the post states as a rule:
- Mutation, which allows modification of the body content itself as well as rejection of an API request.
- Validation, which allows introspection queries and rejection of an API request.
An admission plugin can be in both phases, but all mutation happens before validation.
Its worked example of a plugin in both phases is PodNodeSelector, and the post describes it
twice. In the mutation phase it "uses an annotation on a namespace
namespace.annotations[“scheduler.alpha.kubernetes.io/node-selector”] to find a label selector and
add it to the pod.spec.nodeselector field." In the validation phase the same plugin "ensures
that all pods’ spec.nodeSelector fields are constrained by the node selector restrictions on the
namespace. Even if a mutating admission plugin tries to change the spec.nodeSelector field after
the PodNodeSelector runs in the mutating chain, the PodNodeSelector in the validating chain
prevents the API resource from being created because it fails validation." Its two other named
compiled plugins are PodSecurityPolicy ("prevents escalating containers") and ResourceQuota.
Four use cases follow, numbered, and they are the post's argument for why you would want this:
- Mutation of resources like pods. Istio has talked about doing this to inject side-car containers into pods. You could also write a plugin which forcefully resolves image tags into image SHAs.
- Name restrictions. On multi-tenant systems, reserving namespaces has emerged as a use-case.
- Complex CustomResource validation. Because the entire object is visible, a clever admission plugin can perform complex validation on dependent fields (A requires B) and even external resources (compare to LimitRanges).
- Security response. If you forced image tags into image SHAs, you could write an admission plugin that prevents certain SHAs from running.
Then registration, five things a webhook must declare — how to connect, how to verify the server, which URL path, which resources and verbs, and "What an API server should do on connection failures" — and the manifest. This is reproduced here exactly as the pinned source carries it, because how it is carried is half the exercise:
1 apiVersion: admissionregistration.k8s.io/v1beta1
2 kind: ValidatingWebhookConfiguration
3 metadata:
4 name: namespacereservations.admission.online.openshift.io
5 webhooks:
6 - name: namespacereservations.admission.online.openshift.io
7 clientConfig:
8 service:
9 namespace: default
10 name: kubernetes
11 path: /apis/admission.online.openshift.io/v1alpha1/namespacereservations
12 caBundle: KUBE\_CA\_HERE
13 rules:
14 - operations:
15 - CREATE
16 apiGroups:
17 - ""
18 apiVersions:
19 - "\*"
20 resources:
21 - namespaces
22 failurePolicy: Fail
Four of those twenty-two lines are then annotated by number, and the last annotation is the one the
post cares most about: "Line 22: failurePolicy - says what to do if the webhook admission server
is unavailable. Choices are “Ignore” (fail open) or “Fail” (fail closed). Failing open makes for
unpredictable behavior for all clients." The manifest sets Fail, which in v1beta1 was not the
default, so this is a deliberate choice the post argues for.
It closes on topology, and here it is at its most confident. The recommendation is to build your webhook server as an extension API server and aggregate it:
Simple, secure, portable, zero-config topology¶
If you build your webhook admission server to also be an extension API server, it becomes possible to aggregate it as a normal API server.
Four advantages are listed for that shape — the webhook becomes reachable at
kubernetes.default.svc so "you can test using kubectl"; it inherits in-cluster authentication
and authorization "(without any config)"; extension API servers reach it with their own
in-cluster credentials, also without config; and "Extension API servers do not leak their service
account token to your webhook because they go through kube-apiserver, which is a secure front
proxy." The section ends: "The topology above is zero-config and portable to every Kubernetes
cluster." For the alternatives the post is blunt — "Other topologies are possible but require
additional manual configuration as well as a lot of effort to create a secure setup" — and for
externally hosted webhooks it says flatly that "there is really no alternative to configuring that
file manually."
Two libraries are offered to make the 200-line server real, generic-admission-apiserver and
kubernetes-namespace-reservation, both openshift/. The manifest's names —
namespacereservations.admission.online.openshift.io, and a clientConfig.service.path under
/apis/admission.online.openshift.io/v1alpha1/ — are that second project, so the manifest is not
an invented illustration. It is the registration record of a webhook that was running in
production somewhere, pasted into a blog post.
As it runs now — five distinct failures, and the difference between them is the exercise.
It cannot be copied. The fence above is verbatim from the pinned source. The gutter 1–22 is
inside the fence, and so are Blogger's escapes: KUBE\_CA\_HERE on line 12, "\*" on line 19.
Markdown does not process escapes inside a fenced block, so a reader on kubernetes.io today sees
the backslashes and the numbers on the page. Nothing about this is an API question; the manifest
has to be retyped before any version of Kubernetes can reject it. The same class of artefact is in
the prose — the curly quotes inside the inline code span
namespace.annotations[“scheduler.alpha.kubernetes.io/node-selector”] are the annotation key with
two characters that are not in it.
It errors, first on the group version. admissionregistration.k8s.io/v1beta1 has not been served
since v1.22 (deprecation-guide.md:172-175), so a retyped manifest fails on the kind before any
field is looked at.
It errors again, on two fields that did not exist when it was written. Change the version to v1
and the API server rejects the object for missing admissionReviewVersions and sideEffects.
Neither word appears anywhere in the post. extensible-admission-controllers.md:353 states the
first one as flatly as it can be stated — "admissionReviewVersions is a required field when
creating webhook configurations" — and the API reference marks it required with a bare asterisk
(validating-webhook-configuration-v1.md:97). sideEffects is required and constrained: only
None and NoneOnDryRun are permitted for v1 (deprecation-guide.md:183-184), which means the
post's webhook must now declare something the post never asks it to consider.
It errors a third time, on a placeholder. caBundle: KUBE\_CA\_HERE is not valid base64. It is
also not required: "caBundle is a PEM encoded CA bundle which will be used to validate the
webhook's server certificate. If unspecified, system trust roots on the apiserver are used"
(webhook-client-config-v1-admissionregistration.md:40). So the field can be deleted rather than
filled, and deleting it changes the trust model without saying so.
Then it succeeds, and four things you did not write are now true. A registration that clears all
of the above is accepted, and reading it back shows defaults the post's era did not have.
failurePolicy is the one the post chose, so it looks unchanged — but it is now also the default
(extensible-admission-controllers.md:1154), which means it is no longer evidence of a decision.
matchPolicy defaults to Equivalent where v1beta1 defaulted to Exact; timeoutSeconds is 10
where v1beta1 gave 30 (extensible-admission-controllers.md:1080); scope is "*". All four
crossings are recorded in one place, deprecation-guide.md:178-186, and the why for each is in
research/blog-era-translation.md rather than re-derived
here.
And one thing succeeds while doing nothing, silently. The post's rule names namespaces in the
core group explicitly, so it still matches. But a rule of the shape the pinned page itself now
prints as an example — apiGroups: ["*"], resources: ["*"] — no longer reaches TokenReview,
SubjectAccessReview, SelfSubjectReview, LocalSubjectAccessReview, SelfSubjectAccessReview or
SelfSubjectRulesReview, as of v1.37 (extensible-admission-controllers.md:680-717). A rule that
names one of them outright gets a warning; a wildcard that used to cover them gets nothing, and the
page says why: "Rules that match these resources only through a wildcard ("*") in apiGroups or
resources are not flagged, because their intent is ambiguous" (:707-709).
The diff, and why — this post is on the winning side of its own argument and it is still almost entirely unusable, which is a combination the verdict vocabulary has no word for. Five things moved, in four different directions.
The post broke, and the release is v1.22. Nothing about admission webhooks was withdrawn; the
group version was. admissionregistration.k8s.io/v1 has been available since v1.16
(deprecation-guide.md:175) and v1beta1 stopped being served in v1.22. The five field-level
crossings at deprecation-guide.md:178-186 are not a rename — each one is a default the project
decided had been wrong, and
research/blog-era-translation.md names the pressure:
Ignore by default meant a security webhook failed open, and Exact meant a webhook registered
for one group version never saw the same object submitted under another. The post is a casualty of
its own case being accepted. It argued for fail-closed in a release where fail-open was the
default, and four releases later fail-closed was the default — which is why the one field the
post sets by hand is the only one a reader today would not think to check.
The post was wrong when it was published, in the smallest possible way. Line numbers and
backslashes inside a code fence are not a Kubernetes fact and no release fixed them, because there
is nothing in Kubernetes to fix. The post's only artefact never worked as printed, and a reader who
assumes it did will read the resulting parse error as a cluster problem. The precedent for treating
this as a diff rather than a typo is #67, which
found two 2019 posts with typographic quotes in code fences; this is the same failure a year
earlier, in a different flavour, on a post whose fence is the whole reason it earned a walk.
The project abandoned the plan, and the plan was the topology. The post's "simple, secure,
portable, zero-config" recommendation — make the webhook server an aggregated extension API server
— has no counterpart in the pinned tree. Neither extensible-admission-controllers.md nor
admission-webhooks-good-practices.md mentions aggregation as a webhook deployment shape at all;
the single occurrence of the word in either page is about aggregated API servers calling webhooks
(extensible-admission-controllers.md:243). What replaced it is not a better version of the same
idea. admission-webhooks-good-practices.md tells you to run the webhook as an ordinary Service
with several backends behind it, in a high-availability deployment, with a small timeout
(:209-237) — the
shape the post dismissed as "a lot of effort to create a secure setup". And the post's flat claim
that manual kubeconfig is the only option for external webhooks is now merely almost true: v1.37
added an alpha mechanism for the API server to issue short-lived ServiceAccount tokens bound to a
specific webhook configuration and scoped by API group. Read the note that ships with it before
counting it as the answer, though — "Automatic token acquisition and presentation by the
kube-apiserver and aggregated API servers when calling webhooks, along with a webhook-side token
verification library, are not part of this mechanism" (:241-245). The mechanism issues the
token. Nothing yet carries it.
The post is still right about the thing it was not aware it was arguing. Its rule that "all
mutation happens before validation" holds, and the pinned page turns it into advice: "Admission
webhooks that need to guarantee they see the final state of the object in order to enforce policy
should use a validating admission webhook, since objects can be modified after being seen by
mutating webhooks" (extensible-admission-controllers.md:33-34). But the mechanism the post names
for controlling order within the mutating phase is gone from the documentation. The post says of
the webhook name field: "For mutating webhooks, these are sorted to provide ordering." No page
in the pinned admission set says that. What they say instead is "A single ordering of mutating
admissions plugins (including webhooks) does not work for all cases"
(extensible-admission-controllers.md:1084) and "Mutating admission webhooks don't run in a
consistent order" (admission-webhooks-good-practices.md:465). The replacement is
reinvocationPolicy, defaulting to Never, with three warnings attached, of which the sharpest is
that "The number of additional invocations is not guaranteed to be exactly one"
(:1101). So the post's ordering rule survived and the post's ordering mechanism was
withdrawn — and a reader who takes the naming advice will build a dependency on something that is
now documented as unreliable.
And the post's own worked example has been overtaken by stasis. PodNodeSelector is still
{{< feature-state for_k8s_version="v1.5" state="alpha" >}} at the pin
(admission-controllers.md:682) — alpha since the release before this post's predecessor,
still disabled by default, still keyed on scheduler.alpha.kubernetes.io/node-selector
(:718), and its configuration file still carries the promise that "the configuration file format
will move to a versioned file in a future release" (:694). Nine years of future releases have
gone by. And the pin disagrees with the post about what the plugin does: the post puts it in both
chains, while the page labels it Type: Validating (:684) one line before describing it as one
that "defaults and limits what node selectors may be used within a namespace" (:686) —
defaulting being mutation. The post and the label cannot both be right, and the pinned page
contradicts itself in two consecutive lines, so this exercise records the disagreement rather than
resolving it. The post's second named plugin fared worse: PodSecurityPolicy appears once in the
whole pinned tree, in a sentence about its own replacement (admission-controllers.md:762).
The use cases are the measuring instrument. The post's four reasons to want a webhook, read against the pin one at a time, are what tell you how far this went:
| # | the post's use case | at the pin |
|---|---|---|
| 1 | Mutate pods — "Istio has talked about doing this to inject side-car containers" | Still a webhook case, and named as one: "Make complex modifications that require advanced logic, like calling external APIs" (admission-webhooks-good-practices.md:99-100). Istio appears five times in the pinned docs and not once in connection with admission (glossary, ingress controllers, a sidecar-conversation link) |
| 2 | Name restrictions — reserving namespaces | A ValidatingAdmissionPolicy, with no server, no Service, no CA bundle and no failure-mode decision to make |
| 3 | Complex CustomResource validation | Told not to: "don't use admission webhooks to validate values in CustomResource specifications or to set default values for fields" (admission-webhooks-good-practices.md:146-147), because CRDs carry validation rules and defaulting themselves |
| 4 | Security response — block particular image SHAs | Never modernised in-tree. ImagePolicyWebhook is still documented, still disabled by default, still configured by a file on the API server's disk (admission-controllers.md:323-329) |
And the pinned guidance closes the argument the post opened. Its four-mechanism table
(admission-webhooks-good-practices.md:83-134) is followed by one sentence: "The Kubernetes
project recommends that you use CEL-based admission control when possible" (:139-140). The post
made admission extensible from outside the API server process. The project then spent six releases
building a way to do most of it back inside.
No gate — for the post's subject, and this is the finding rather than a gap in the search.
There is no feature gate for admission webhooks. No file in the 487 under feature-gates/ is named
for MutatingAdmissionWebhook, ValidatingAdmissionWebhook, dynamic admission or extensible
admission; the mechanism went from beta in v1.9 to a served v1 in v1.16 and its history is
readable only from deprecation-guide.md:172-186 and from the fact that both plugins ship in the
default-enabled set — which admission-controllers.md:916-918 asserts without printing ("The
recommended admission controllers are enabled by default", the list itself deferred to the
kube-apiserver options reference; the v1.37 list is in
research/blog-era-translation.md). The two plugin
sections are admission-controllers.md:519 and :897. Five gate files mention
admission webhooks in their bodies. Every one of them is for something built around the mechanism
after this post, and their ladders are below.
The three that replace it. ValidatingAdmissionPolicy:
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.26 – v1.27 |
| beta | false |
— | v1.28 – v1.29 |
| stable | true |
— | v1.30 – v1.31 |
The file declares removed: true — the gate is gone, the feature is unconditional, and
validating-admission-policy.md:12 carries state="stable" for_k8s_version="v1.30" to match.
MutatingAdmissionPolicy:
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.30 – v1.33 |
| beta | false |
— | v1.34 – v1.35 |
| stable | true |
— | v1.36 – |
No removed:, so this gate still exists at the pin even though its stage is stable. And the body
of the gate file carries a fact the table cannot hold: "For Kubernetes v1.30 and v1.31, this
feature gate existed but had no effect." Two of the four releases in the alpha row are a gate you
could set that did nothing.
research/blog-era-translation.md dates this feature's
alpha to v1.32 from KEP-3962, which is the first release the gate did anything; the gate file dates
it to v1.30, which is the first release the gate existed. Both are recorded; they are answering
different questions. ManifestBasedAdmissionControlConfig:
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.36 – v1.36 |
| beta | true |
— | v1.37 – |
One release of alpha, then on by default (manifest-admission-control.md:26). This is the newest
mechanism in the pin and the one that most directly answers the post's "Registration" section:
webhook configurations and CEL policies can be loaded from files on disk instead of from the API,
which the page justifies with three limitations of API registration — a bootstrap gap, a
self-protection gap, and an etcd dependency (:32-46). The post's fifth registration fact was what
the API server should do on connection failures. The pin's answer includes not having to be
registered through the API at all.
The two that modify it. AdmissionWebhookMatchConditions:
| stage | default | locked | releases |
|---|---|---|---|
| alpha | false |
— | v1.27 – v1.27 |
| beta | true |
— | v1.28 – v1.29 |
| stable | true |
— | v1.30 – v1.32 |
removed: true. This is the only one of the five that changes the post's own rules block: a
second filter, up to 64 CEL expressions per webhook, evaluated before the call
(extensible-admission-controllers.md:861-950). Its cost validation has its own gate,
StrictCostEnforcementForWebhooks — beta false v1.31, stable true from v1.32 — which is a
gate for the enforcement of a limit on a filter on the mechanism the post announces, three
removes from the post. And ExcludeAdmissionWebhookVirtualResources:
| stage | default | locked | releases |
|---|---|---|---|
| beta | true |
— | v1.37 – |
Straight to beta and on, in the pin's own release, with the deprecation of the old behaviour dated
in prose rather than in the ladder: "Webhook interception of these virtual resources is deprecated
as of Kubernetes v1.37", and the gate "is planned to be locked to enabled when this feature
graduates to stable" (extensible-admission-controllers.md:711-717). A locked column that is
— today with a stated plan to become true is the clearest reading in this file of why locked
is a column and not a suffix.
Read the five together and the shape is this: the post's mechanism has no gate because it is finished, and everything gated around it is either a way to call it less often or a way not to call it at all.
Topology — solo: one node at 10.10.10.180. Every
step below is an API-server decision — registration validation, defaulting, admission-time
rejection — and none of it needs a second node or a running Pod. Bring it up with
the five provision steps, substituting
topology=solo, then
the node baseline, then
ssh zain@10.10.10.180.
Say plainly what this topology cannot show. It never runs a webhook server, so nothing here
observes an AdmissionReview on the wire or a webhook returning allowed: false. That is
deliberate and it is the exercise's title: every failure below lands before the call. A webhook that
is never reached is the post's own failurePolicy sentence, tested.
Do
- Confirm which versions of the post's API group your cluster serves, and compare against the pinned reference.
kubectl api-versions | grep admissionregistration
kubectl api-resources --api-group=admissionregistration.k8s.io
group-versions.md:13 lists three versions for this group — v1, v1beta1, v1alpha1. Note which
of them your cluster answers for, and note that all six kinds in
docs/reference/kubernetes-api/admissionregistration/ are -v1 pages.
- Retype the post's manifest into
post-webhook.yaml, without the gutter and without the backslashes, changing nothing else. Then apply it.
-
Change only
apiVersiontoadmissionregistration.k8s.io/v1and apply again. Read the error to the end and count the field paths it names. -
Add the two required fields and nothing else —
admissionReviewVersions: ["v1"]andsideEffects: Noneon the webhook entry — and apply again. -
Delete the
caBundleline entirely and apply again. Before you do, decide what deleting it changes about who the API server will trust, then check your answer againstwebhook-client-config-v1-admissionregistration.md:40. -
Read back what the API server stored, and diff it against what you sent.
kubectl get validatingwebhookconfiguration \
namespacereservations.admission.online.openshift.io \
-o yaml | grep -E 'failurePolicy|matchPolicy|timeoutSeconds|scope|sideEffects|admissionReviewVersions'
Four of these you did not write. One of them the post wrote by hand for a reason it explains.
Say which is which, then find all five in deprecation-guide.md:178-186.
- Now find out what the registered webhook costs. The rule is
CREATEon corenamespaces, thefailurePolicyisFail, andclientConfig.servicepoints at thekubernetesService indefaultat an OpenShift path that has never existed on your cluster.
- Undo it, and confirm the cluster is whole again.
kubectl delete validatingwebhookconfiguration \
namespacereservations.admission.online.openshift.io
kubectl create namespace probe && kubectl delete namespace probe
-
Re-register the webhook with one
matchConditionsentry that is valid CEL and evaluates false for this namespace — for example a condition namedonly-team-namespaceswith the expressionobject.metadata.name.startsWith("team-")— then create a namespace whose name does not start withteam-. The configuration is registered, the rule matches, and the request succeeds. Explain, fromextensible-admission-controllers.md:945-950, why a request that matched the rule was admitted without the webhook being contacted, and what would have happened instead if the expression had errored rather than returned false. Then delete the configuration. -
Register a webhook whose rule explicitly names
subjectaccessreviewsin theauthorization.k8s.iogroup, and read whatkubectlprints alongside the success.Then change that rule to
apiGroups: ["*"], resources: ["*"]and apply again. One of these two tells you the rule has no effect and the other does not, and the reason is atextensible-admission-controllers.md:703-710. Delete the configuration. -
Do the post's use case 2 with no webhook server at all. Apply the pinned tree's own ValidatingAdmissionPolicy pair from
content/en/examples/validatingadmissionpolicy/basic-example-policy.yamlandbasic-example-binding.yaml— a policy requiringobject.spec.replicas <= 5onapps/v1deployments, bound to namespaces labelledenvironment: test. Label a namespace to match, then test both sides against the server without scheduling anything:kubectl label namespace default environment=test kubectl create deployment six --image=registry.k8s.io/pause:3.10 --replicas=6 --dry-run=server kubectl create deployment four --image=registry.k8s.io/pause:3.10 --replicas=4 --dry-run=server--dry-run=serverruns the full admission chain, which is why step 4'ssideEffects: Nonematters: a webhook that has not declared itself dry-run-safe is not sent dry-run requests at all (extensible-admission-controllers.md:1039-1040). Note that no Service, no certificate and nofailurePolicydecision was involved in any of this. -
Find the two places the pinned tree contradicts itself about the mechanism from step 11.
admission-controllers.md:894says ValidatingAdmissionPolicy "is enabled when both feature gatevalidatingadmissionpolicyandadmissionregistration.k8s.io/v1alpha1group/version are enabled", while its gate file recordsstablefrom v1.30 andremoved: true. Andvalidating-admission-policy.md:298, on the stable page, gives aValidatingAdmissionPolicyBindingexample atadmissionregistration.k8s.io/v1alpha1where every other example on the page usesv1. Apply that one example and see what your cluster says. -
Ask the list where the fourth mechanism went.
admission-controllers.md:43-46claims to list the admission controllers in this Kubernetes version, and:50-54names exactly three extension points — MutatingAdmissionWebhook, ValidatingAdmissionWebhook and ValidatingAdmissionPolicy.MutatingAdmissionPolicyappears nowhere in that file. Its gate saysstable,true, from v1.36. -
Last, the post's worked example, which needs no cluster change to check.
kubectl get --raw /metrics | grep -c apiserver_admission_webhook_rejection_count tr ' ' '\n' < /proc/"$(pgrep kube-apiserver)"/cmdline | grep -E 'admission|feature-gates'Then read
admission-controllers.md:680-694and answer whetherPodNodeSelectoris running on your cluster, whether it could be, and how many releases have passed since the note about the versioned configuration file was written.
Expect
Step 1 — admissionregistration.k8s.io/v1 at minimum, and six resources:
mutatingwebhookconfigurations, validatingwebhookconfigurations, validatingadmissionpolicies,
validatingadmissionpolicybindings, mutatingadmissionpolicies, mutatingadmissionpolicybindings.
Alpha group versions are not served unless enabled (using-api/_index.md:108-116), so if
v1alpha1 is absent from api-versions while group-versions.md:13 lists it, you have found the
difference between what the API can serve and what your API server was started with.
Step 2 — a failure naming the kind, of the form
error: resource mapping not found for name: "namespacereservations.admission.online.openshift.io"
... no matches for kind "ValidatingWebhookConfiguration" in version
"admissionregistration.k8s.io/v1beta1". Not a field error. The object was never parsed as a
webhook configuration.
Step 3 — a validation error naming exactly two paths,
webhooks[0].sideEffects and webhooks[0].admissionReviewVersions, each Required value. If you
see three, you left the caBundle placeholder in and the third is webhooks[0].clientConfig.caBundle
failing to decode.
Step 4 — either success, or the caBundle error alone.
Step 5 — validatingwebhookconfiguration.admissionregistration.k8s.io/namespacereservations.admission.online.openshift.io
created.
Step 6 — failurePolicy: Fail, matchPolicy: Equivalent, timeoutSeconds: 10, scope: '*',
plus the two fields you added. The post wrote one of those five values; the API server chose the
other four.
Step 7 — the namespace is not created, and the error names the webhook rather than the
namespace: Error from server (InternalError): Internal error occurred: failed calling webhook
"namespacereservations.admission.online.openshift.io". Read the tail of that message for whether
the API server got a connection error or an HTTP status, and match it against the four error
classes at extensible-admission-controllers.md:1131-1136.
Step 8 — the delete succeeds, and so does the namespace. If the delete had also been blocked you
would have found the failure mode manifest-admission-control.md:40-43 calls the self-protection
gap; it is not blocked here because the post's rule is scoped to namespaces.
Step 9 — namespace/probe created, with the webhook configuration still registered and still
failurePolicy: Fail. The webhook was skipped, not called and not failed.
Step 10 — the explicit rule is accepted with a warning that the rule has no effect; the wildcard rule is accepted silently. Both webhooks are registered and neither will ever see a SubjectAccessReview.
Step 11 — the six-replica dry run is denied with a message of the form ValidatingAdmissionPolicy
'demo-policy.example.com' with binding 'demo-binding-test.example.com' denied request: failed
expression: object.spec.replicas <= 5 (validating-admission-policy.md:85-87); the four-replica
dry run reports deployment.apps/four created (server dry run).
Step 12 — the v1alpha1 binding does not create anything on a default cluster. Whether the error
names the version or the kind tells you which of the two claims in this step is stale prose and
which is a live API question.
Step 13 — six resource names including mutatingadmissionpolicies, from a group whose reference
list of admission controllers does not mention the kind.
Step 14 — a non-zero count for the rejection-count metric line, and no PodNodeSelector in the API
server's arguments. It is disabled by default and kubeadm does not enable it.
Read on — four questions. Three are answerable from the pinned tree; the fourth is the one to carry.
manifest-admission-control.md:341cites KEP-5793 for manifest-based admission control. Read the three limitations of API-registered admission at:31-47and ask which of them the post's own manifest is an instance of — it registers a webhook whoseclientConfigpoints at thekubernetesService indefault, so the webhook that gates namespace creation is reached through the same API server whose availability it depends on.extensible-admission-controllers.md:1082-1121is the replacement for the post's sentence about webhook names being sorted. Read the section and ask whyreinvocationPolicydefaults toNeverwhen the problem it solves — a mutating webhook not seeing a later webhook's changes — is the exact problem the post's ordering advice was for.admission-webhooks-good-practices.md:398-418tells you to setfailurePolicy: Ignoreon mutating webhooks and validate the final state separately. The post says "Failing open makes for unpredictable behavior for all clients", and v1 madeFailthe default (extensible-admission-controllers.md:1154). Both are in the pin. Work out what distinction reconciles them, and which of the two the post's ownValidatingWebhookConfigurationfalls under.- The unanswerable one. The post's recommended topology — the webhook as an aggregated extension
API server — is not deprecated anywhere in the pinned tree, is not argued against anywhere, and
is not mentioned.
admission-webhooks-good-practices.mdis 651 lines of deployment advice for a shape the post called "a lot of effort". Nothing in the pin records a decision to stop recommending aggregation; the recommendation simply is not there. Whether it was withdrawn, or whether the page that would have carried it was written by people who had never read this post, is not in the pinned tree. It is the difference between a plan the project abandoned and a plan the project forgot, and the archive can tell you which only by not finding it.
Sibling exercises worth reading first, both backward: RBAC went GA one release before this post
(../2017/06-using-rbac-generally-available-18.md),
and admission runs after authorization, so the authorizer CEL variable in step 9's mechanism and
the attest permission the v1.37 token mechanism requires are both RBAC objects. And the post's
fourth use case — blocking particular image SHAs — was already a forecast in 2016
(../2016/08-security-best-practices-kubernetes-deployment.md),
where it appears as "image authorization plugins (expected in Kubernetes 1.4)" with a link to an
open pull request. Reading them in order gives you the same feature promised in 2016, offered as a
use case in 2018, and still an off-by-default file-configured plugin at the pin.
Teardown — leave the cluster up. The next exercise in this year's table needs a worker node, so
solo goes away when you get there; until then this cluster is the cheapest place to keep poking at
admission. Delete every object this exercise created first — both webhook
configurations, the policy, the binding and the environment label on default — because a
failurePolicy: Fail webhook left registered will make the next exercise's first kubectl apply
fail for a reason that has nothing to do with storage:
kubectl delete validatingwebhookconfiguration --all
kubectl delete validatingadmissionpolicybinding demo-binding-test.example.com
kubectl delete validatingadmissionpolicy demo-policy.example.com
kubectl label namespace default environment-
When you are done with solo entirely, tear it down.