Warning
This project is deprecated and the repository is archived.
Kubernetes now ships MutatingAdmissionPolicy,
an in-process, CEL-based mutating admission mechanism that replaces this
webhook entirely — no server, Deployment, Service, or TLS certificate
management required. See Recommended replacement below.
MutatingAdmissionPolicy is GA in Kubernetes 1.36+ and works out of the
box via admissionregistration.k8s.io/v1. On 1.34/1.35 it exists only as
v1beta1, which is off by default — it requires the
MutatingAdmissionPolicy feature gate and
--runtime-config=admissionregistration.k8s.io/v1beta1=true on the API
server, so on a stock 1.35 cluster it won't work. Upgrade to 1.36 (or enable
those flags) to use it.
The published container images remain available for anyone still running the webhook, but the source, manifests, and CI have been removed and no further updates will be made.
Kubernetes services are created in single stack mode by default, defaulting to the primary address family of the cluster.
This was a mutating webhook admission controller that adds
ipFamilyPolicy: PreferDualStack to all newly created services if the field is
not explicitly specified, making dual-stack the default.
On any cluster running Kubernetes 1.34 or newer, the same behaviour can be
achieved declaratively with a MutatingAdmissionPolicy and a binding — no
webhook server to deploy or certificates to rotate.
apiVersion: admissionregistration.k8s.io/v1 # GA in 1.36+; on 1.34/1.35 use v1beta1 with the feature gate + runtime-config enabled
kind: MutatingAdmissionPolicy
metadata:
name: prefer-dual-stack-services
spec:
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["services"]
matchConditions:
- name: ipfamilypolicy-unset
expression: "!has(object.spec.ipFamilyPolicy)"
- name: not-externalname
expression: 'object.spec.type != "ExternalName"'
failurePolicy: Ignore
reinvocationPolicy: Never
mutations:
- patchType: ApplyConfiguration
applyConfiguration:
expression: >
Object{
spec: Object.spec{
ipFamilyPolicy: "PreferDualStack"
}
}
---
apiVersion: admissionregistration.k8s.io/v1 # GA in 1.36+; on 1.34/1.35 use v1beta1 with the feature gate + runtime-config enabled
kind: MutatingAdmissionPolicyBinding
metadata:
name: prefer-dual-stack-services-binding
spec:
policyName: prefer-dual-stack-servicesHow this maps to the old webhook:
- The
matchConditionsreproduce the original guards: only apply whenspec.ipFamilyPolicyis unset and the service is not of typeExternalName(the!= "ExternalName"check holds whethertypeis empty orClusterIP). operations: ["CREATE"]matches the webhook's create-only behaviour.ApplyConfigurationis used becauseipFamilyPolicyis a plain scalar on a non-atomic struct. If you ever need to mutate atomic structs/maps/lists, usepatchType: JSONPatchinstead.failurePolicy: Ignoremirrors the webhook's "never get in the way" posture; set it toFailif you'd rather block service creation on policy errors.
| Before | After |
|---|---|
> kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: dummy
spec:
selector:
app: dummy
ports:
- port: 80
targetPort: 80
EOF
service/dummy created
> kubectl describe svc dummy
Name: dummy
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=dummy
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.96.200.4
IPs: 10.96.200.4
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: <none>
Session Affinity: None
Events: <none> |
> kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: dummy
spec:
selector:
app: dummy
ports:
- port: 80
targetPort: 80
EOF
service/dummy created
> kubectl describe svc dummy
Name: dummy
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=dummy
Type: ClusterIP
IP Family Policy: PreferDualStack
IP Families: IPv4,IPv6
IP: 10.96.19.47
IPs: 10.96.19.47,fd00:10:96::f31
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: <none>
Session Affinity: None
Events: <none> |
Admission controller webhook boilerplate adapted from douglasmakey/admissioncontroller by Douglas Makey Mendez Molero.