-
Notifications
You must be signed in to change notification settings - Fork 50
POC: using MutatingAdmissionPolicy to schedule CS pods #1777
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package e2e | ||
|
||
import ( | ||
"path/filepath" | ||
"testing" | ||
"time" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"kpt.dev/configsync/e2e/nomostest" | ||
"kpt.dev/configsync/e2e/nomostest/ntopts" | ||
"kpt.dev/configsync/e2e/nomostest/syncsource" | ||
nomostesting "kpt.dev/configsync/e2e/nomostest/testing" | ||
"kpt.dev/configsync/e2e/nomostest/testpredicates" | ||
"kpt.dev/configsync/pkg/api/configsync" | ||
"kpt.dev/configsync/pkg/core/k8sobjects" | ||
"kpt.dev/configsync/pkg/kinds" | ||
"kpt.dev/configsync/pkg/reconcilermanager" | ||
) | ||
|
||
// This test currently requires KinD because MutatingAdmissionPolicy is alpha | ||
// and requires a feature gate. | ||
func TestMutatingAdmissionPolicy(t *testing.T) { | ||
nt := nomostest.New(t, nomostesting.Reconciliation2, | ||
ntopts.SyncWithGitSource(nomostest.DefaultRootSyncID, ntopts.Unstructured), | ||
ntopts.RequireKind(t)) | ||
rootSyncGitRepo := nt.SyncSourceGitReadWriteRepository(nomostest.DefaultRootSyncID) | ||
|
||
mapFile := filepath.Join(".", "..", "..", "examples", "mutating-admission-policies", "config-sync-node-placement.yaml") | ||
nt.T.Cleanup(func() { | ||
nt.Must(nt.Shell.Kubectl("delete", "--ignore-not-found", "-f", mapFile)) | ||
}) | ||
nt.Must(nt.Shell.Kubectl("apply", "-f", mapFile)) | ||
// TODO: is there a way to wait for MutatingAdmissionPolicy readiness? (it doesn't appear so) | ||
// sleep hack to give time to propagate | ||
time.Sleep(5 * time.Second) | ||
|
||
// expected nodeAffinity from the example MutatingAdmissionPolicy yaml | ||
exampleNodeAffinity := &corev1.NodeAffinity{ | ||
PreferredDuringSchedulingIgnoredDuringExecution: []corev1.PreferredSchedulingTerm{ | ||
{ | ||
Weight: 1, | ||
Preference: corev1.NodeSelectorTerm{ | ||
MatchExpressions: []corev1.NodeSelectorRequirement{ | ||
{ | ||
Key: "another-node-label-key", | ||
Operator: corev1.NodeSelectorOpIn, | ||
Values: []string{"another-node-label-value"}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
// bounce reconciler-manager Pod and verify the nodeAffinity is applied by MAP | ||
nt.Must(nomostest.ValidatePodByLabel(nt, "app", reconcilermanager.ManagerName, | ||
testpredicates.HasExactlyNodeAffinity(&corev1.NodeAffinity{}))) | ||
nt.T.Log("Replacing the reconciler-manager Pod to validate nodeAffinity is added") | ||
nomostest.DeletePodByLabel(nt, "app", reconcilermanager.ManagerName, false) | ||
nt.Must(nomostest.ValidatePodByLabel(nt, "app", reconcilermanager.ManagerName, | ||
testpredicates.HasExactlyNodeAffinity(exampleNodeAffinity))) | ||
|
||
// update the RootSync to trigger a Deployment change, verify new reconciler Pod has nodeAffinity | ||
rootSync := nomostest.RootSyncObjectV1Beta1FromRootRepo(nt, nomostest.DefaultRootSyncID.Name) | ||
rootSync.Spec.Git.Dir = "foo" | ||
nt.Must(nt.KubeClient.Apply(rootSync)) | ||
nt.Must(rootSyncGitRepo.Add("foo/ns.yaml", k8sobjects.NamespaceObject("test-map-ns"))) | ||
nt.Must(rootSyncGitRepo.CommitAndPush("add foo-ns under foo/ dir")) | ||
nt.Must(nt.WatchForSync(kinds.RootSyncV1Beta1(), rootSync.Name, configsync.ControllerNamespace, | ||
&syncsource.GitSyncSource{ | ||
ExpectedCommit: rootSyncGitRepo.MustHash(t), | ||
ExpectedDirectory: "foo", | ||
})) | ||
nt.Must(nt.Validate("test-map-ns", "", &corev1.Namespace{})) | ||
nt.Must(nomostest.ValidatePodByLabel(nt, "app", reconcilermanager.Reconciler, | ||
testpredicates.HasExactlyNodeAffinity(exampleNodeAffinity))) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Scheduling Config Sync system pods using MutatingAdmissionPolicies | ||
|
||
[MutatingAdmissionPolicy] can be used to configure the way the Config Sync's | ||
system Pods are scheduled. | ||
|
||
The [example in this directory](./config-sync-node-placement.yaml) demonstrates | ||
how to inject `nodeAffinity` into all Pods in the `config-management-system` Namespace. | ||
|
||
Note that this is just a demonstrative example, and different `matchConstraints` and | ||
`mutations` can be applied depending on the use case. | ||
|
||
Caveats: | ||
|
||
- MutatingAdmissionPolicy is currently in alpha and requires feature gate enablement. It is [targeted to enter beta in k8s 1.34]. | ||
- MutatingAdmissionPolicy does not update existing Pods. Pods created before the policy was applied must be updated/recreated. | ||
|
||
|
||
|
||
|
||
[MutatingAdmissionPolicy]: https://kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/ | ||
[targeted to enter beta in k8s 1.34]: https://github.com/kubernetes/enhancements/issues/3962 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
apiVersion: admissionregistration.k8s.io/v1alpha1 | ||
kind: MutatingAdmissionPolicy | ||
metadata: | ||
name: "configsync-nodeplacement" | ||
spec: | ||
# This example uniformly applies the nodeAffinity to *all* Pods in config-management-system. | ||
# Different matchConstraints can be used for more granular mutation. | ||
matchConstraints: | ||
namespaceSelector: | ||
matchLabels: | ||
kubernetes.io/metadata.name: config-management-system | ||
resourceRules: | ||
- apiGroups: [""] | ||
apiVersions: ["v1"] | ||
operations: ["CREATE", "UPDATE"] | ||
resources: ["pods"] | ||
failurePolicy: Fail | ||
reinvocationPolicy: IfNeeded | ||
mutations: | ||
# Simple example of adding nodeAffinity: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ | ||
# Similar mutations can be applied for nodeSelector/tolerations/etc | ||
- patchType: "JSONPatch" | ||
jsonPatch: | ||
expression: > | ||
[ | ||
JSONPatch{ | ||
op: "add", path: "/spec/affinity", | ||
value: Object.spec.affinity{ | ||
nodeAffinity: Object.spec.affinity.nodeAffinity{ | ||
preferredDuringSchedulingIgnoredDuringExecution: [ | ||
Object.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution{ | ||
weight: 1, | ||
preference: Object.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.preference{ | ||
matchExpressions: [ | ||
Object.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution.preference.matchExpressions{ | ||
key: "another-node-label-key", | ||
operator: "In", | ||
values: [ | ||
"another-node-label-value" | ||
] | ||
} | ||
] | ||
} | ||
} | ||
] | ||
} | ||
} | ||
} | ||
] | ||
Comment on lines
+24
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This CEL expression is confusing and appears to use a non-standard syntax for constructing the patch value. Using constructs like For a user-facing example, it's crucial that the code is clear, robust, and follows documented conventions. A better approach would be to use standard CEL to construct the patch, or if using CEL's proto message construction capabilities, to use the correct, fully-qualified type names (e.g., Here is a suggested correction that uses more conventional type names. This assumes the
|
||
--- | ||
apiVersion: admissionregistration.k8s.io/v1alpha1 | ||
kind: MutatingAdmissionPolicyBinding | ||
metadata: | ||
name: "configsync-nodeplacement" | ||
spec: | ||
policyName: "configsync-nodeplacement" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of
time.Sleep
can introduce flakiness into the test suite. WhileMutatingAdmissionPolicy
currently lacks a status field for readiness, a more reliable approach would be to actively poll for the policy to become effective.A robust alternative to a fixed sleep duration would be to attempt to create a resource that the policy should mutate, and retry until the mutation is observed. This would provide a more deterministic signal that the admission policy is active and ready.