k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/common/network-policy/network-policy-enforcement-latency.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package networkpolicy 18 19 import ( 20 "context" 21 "embed" 22 "fmt" 23 "strings" 24 "sync" 25 "time" 26 27 "golang.org/x/time/rate" 28 corev1 "k8s.io/api/core/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 clientset "k8s.io/client-go/kubernetes" 31 "k8s.io/klog/v2" 32 "k8s.io/perf-tests/clusterloader2/pkg/framework" 33 "k8s.io/perf-tests/clusterloader2/pkg/framework/client" 34 "k8s.io/perf-tests/clusterloader2/pkg/measurement" 35 measurementutil "k8s.io/perf-tests/clusterloader2/pkg/measurement/util" 36 "k8s.io/perf-tests/clusterloader2/pkg/util" 37 ) 38 39 /* 40 The measurement tests network policy enforcement latency for two cases: 41 1. Created network policies 42 Deploy the test clients (setup and run) with "testType" flag set to 43 "policy-creation" after creating the target pods. 44 2. Created pods that are affected by network policies 45 Deploy the test clients (setup and run) with "testType" flag set to 46 "pod-creation", before creating the target pods. 47 Target pods are all pods that have the specified label: 48 { net-pol-test: targetLabelValue }. 49 The test is set up by this measurement, by creating the required resources, 50 including the network policy enforcement latency test client pods that are 51 measuring the latencies and generating metrics for them. 52 https://github.com/kubernetes/perf-tests/tree/master/network/tools/network-policy-enforcement-latency 53 */ 54 55 const ( 56 networkPolicyEnforcementName = "NetworkPolicyEnforcement" 57 netPolicyTestNamespace = "net-policy-test" 58 netPolicyTestClientName = "np-test-client" 59 policyCreationTest = "policy-creation" 60 podCreationTest = "pod-creation" 61 // denyLabelValue is used for network policies to allow connections only to 62 // the pods with the specified label, effectively denying other connections, 63 // as long as there isn't another network policy allowing it for other labels. 64 denyLabelValue = "deny-traffic" 65 allowPolicyName = "allow-egress-to-target" 66 denyPolicyName = "deny-egress-to-target" 67 68 serviceAccountFilePath = "manifests/serviceaccount.yaml" 69 clusterRoleFilePath = "manifests/clusterrole.yaml" 70 clusterRoleBindingFilePath = "manifests/clusterrolebinding.yaml" 71 depTestClientPolicyCreationFilePath = "manifests/dep-test-client-policy-creation.yaml" 72 depTestClientPodCreationFilePath = "manifests/dep-test-client-pod-creation.yaml" 73 policyEgressApiserverFilePath = "manifests/policy-egress-allow-apiserver.yaml" 74 policyEgressTargetPodsFilePath = "manifests/policy-egress-allow-target-pods.yaml" 75 policyLoadFilePath = "manifests/policy-load.yaml" 76 77 defaultPolicyTargetLoadBaseName = "small-deployment" 78 defaultPolicyLoadCount = 1000 79 defaultPolicyLoadQPS = 10 80 ) 81 82 //go:embed manifests 83 var manifestsFS embed.FS 84 85 func init() { 86 klog.V(2).Infof("Registering %q", networkPolicyEnforcementName) 87 if err := measurement.Register(networkPolicyEnforcementName, createNetworkPolicyEnforcementMeasurement); err != nil { 88 klog.Fatalf("Cannot register %s: %v", networkPolicyEnforcementName, err) 89 } 90 } 91 92 func createNetworkPolicyEnforcementMeasurement() measurement.Measurement { 93 return &networkPolicyEnforcementMeasurement{} 94 } 95 96 type networkPolicyEnforcementMeasurement struct { 97 k8sClient clientset.Interface 98 framework *framework.Framework 99 // testClientNamespace is the namespace of the test client pods. 100 testClientNamespace string 101 // targetLabelValue is the value for the label selector of target pods to 102 // apply network policies on and measure the latency to become reachable. 103 targetLabelValue string 104 // targetNamespaces is a list of test namespace with "test-" prefix that are 105 // used to specify namespaces of target pods for all test clients. 106 targetNamespaces []string 107 // baseline test does not create network policies. It is only used for pod 108 // creation latency test, to compare pod creation reachability latency with 109 // and without network policies. 110 baseline bool 111 // testClientNodeSelectorValue is value key for the node label on which the 112 // test client pods should run. 113 testClientNodeSelectorValue string 114 } 115 116 // Execute - Available actions: 117 // 1. setup - Loads all the required data to execute run command. Should be 118 // called only once. 119 // 2. run - Runs a test measurement for the specified test type. 120 // 3. complete - Finishes and cleans up the specified test type. 121 func (nps *networkPolicyEnforcementMeasurement) Execute(config *measurement.Config) ([]measurement.Summary, error) { 122 action, err := util.GetString(config.Params, "action") 123 if err != nil { 124 return nil, err 125 } 126 127 switch action { 128 case "setup": 129 return nil, nps.setup(config) 130 case "run": 131 return nil, nps.run(config) 132 case "complete": 133 return nil, nps.complete(config) 134 default: 135 return nil, fmt.Errorf("unknown action %v", action) 136 } 137 } 138 139 // setup initializes the measurement, creates a namespace, network policy egress 140 // allow to the kube-apiserver and permissions for the network policy 141 // enforcement test clients. 142 func (nps *networkPolicyEnforcementMeasurement) setup(config *measurement.Config) error { 143 err := nps.initializeMeasurement(config) 144 if err != nil { 145 return fmt.Errorf("failed to initialize the measurement: %v", err) 146 } 147 148 if err := client.CreateNamespace(nps.k8sClient, nps.testClientNamespace); err != nil { 149 return fmt.Errorf("error while creating namespace: %v", err) 150 } 151 152 // Create network policies for non-baseline test. 153 if !nps.baseline { 154 if err = nps.createPolicyAllowAPIServer(); err != nil { 155 return err 156 } 157 158 // Create a policy that allows egress from pod creation test client pods to 159 // target pods. 160 podCreationAllowPolicyName := fmt.Sprintf("%s-pod-creation", allowPolicyName) 161 if err = nps.createPolicyToTargetPods(podCreationAllowPolicyName, "", podCreationTest, true); err != nil { 162 return err 163 } 164 165 // Create a policy that denies egress from policy creation test client pods 166 // to target pods. 167 policyCreationDenyPolicyName := fmt.Sprintf("%s-policy-creation", denyPolicyName) 168 if err = nps.createPolicyToTargetPods(policyCreationDenyPolicyName, "", policyCreationTest, false); err != nil { 169 return err 170 } 171 } 172 173 return nps.createPermissionResources() 174 } 175 176 func (nps *networkPolicyEnforcementMeasurement) initializeMeasurement(config *measurement.Config) error { 177 if nps.framework != nil { 178 return fmt.Errorf("the %q is already started. Cannot start again", networkPolicyEnforcementName) 179 } 180 181 var err error 182 if nps.targetLabelValue, err = util.GetString(config.Params, "targetLabelValue"); err != nil { 183 return err 184 } 185 186 if nps.testClientNamespace, err = util.GetStringOrDefault(config.Params, "testClientNamespace", netPolicyTestNamespace); err != nil { 187 return err 188 } 189 190 if nps.baseline, err = util.GetBoolOrDefault(config.Params, "baseline", false); err != nil { 191 return err 192 } 193 194 if nps.testClientNodeSelectorValue, err = util.GetString(config.Params, "testClientNodeSelectorValue"); err != nil { 195 return err 196 } 197 198 nps.framework = config.ClusterFramework 199 nps.k8sClient = config.ClusterFramework.GetClientSets().GetClient() 200 201 namespaceList, err := nps.k8sClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) 202 if err != nil { 203 return err 204 } 205 206 // Target namespaces are only those that have a predefined test prefix. 207 testNamespacePrefix := nps.framework.GetAutomanagedNamespacePrefix() 208 for _, ns := range namespaceList.Items { 209 if strings.HasPrefix(ns.GetName(), testNamespacePrefix) { 210 nps.targetNamespaces = append(nps.targetNamespaces, ns.GetName()) 211 } 212 } 213 214 if len(nps.targetNamespaces) == 0 { 215 return fmt.Errorf("cannot initialize the %q, no namespaces with prefix %q exist", networkPolicyEnforcementName, testNamespacePrefix) 216 } 217 218 return nil 219 } 220 221 // createPermissionResources creates ServiceAccount, ClusterRole and 222 // ClusterRoleBinding for the test client pods. 223 func (nps *networkPolicyEnforcementMeasurement) createPermissionResources() error { 224 templateMap := map[string]interface{}{ 225 "Name": netPolicyTestClientName, 226 "Namespace": nps.testClientNamespace, 227 } 228 229 if err := nps.framework.ApplyTemplatedManifests(manifestsFS, serviceAccountFilePath, templateMap); err != nil { 230 return fmt.Errorf("error while creating serviceaccount: %v", err) 231 } 232 233 if err := nps.framework.ApplyTemplatedManifests(manifestsFS, clusterRoleFilePath, templateMap); err != nil { 234 return fmt.Errorf("error while creating clusterrole: %v", err) 235 } 236 237 if err := nps.framework.ApplyTemplatedManifests(manifestsFS, clusterRoleBindingFilePath, templateMap); err != nil { 238 return fmt.Errorf("error while creating clusterrolebinding: %v", err) 239 } 240 241 return nil 242 } 243 244 func (nps *networkPolicyEnforcementMeasurement) run(config *measurement.Config) error { 245 if nps.framework == nil { 246 return fmt.Errorf("the %q is not set up. Execute with the `setup` action before with the `run` action", networkPolicyEnforcementName) 247 } 248 249 targetPort, err := util.GetIntOrDefault(config.Params, "targetPort", 80) 250 if err != nil { 251 return err 252 } 253 254 maxTargets, err := util.GetIntOrDefault(config.Params, "maxTargets", 1000) 255 if err != nil { 256 return err 257 } 258 259 metricsPort, err := util.GetIntOrDefault(config.Params, "metricsPort", 9160) 260 if err != nil { 261 return err 262 } 263 264 testType, err := util.GetString(config.Params, "testType") 265 if err != nil { 266 return err 267 } 268 269 templateMap := map[string]interface{}{ 270 "Namespace": nps.testClientNamespace, 271 "TestClientLabel": netPolicyTestClientName, 272 "TargetLabelSelector": fmt.Sprintf("net-pol-test = %s", nps.targetLabelValue), 273 "TargetPort": targetPort, 274 "MetricsPort": metricsPort, 275 "ServiceAccountName": netPolicyTestClientName, 276 "MaxTargets": maxTargets, 277 "TestClientNodeSelectorValue": nps.testClientNodeSelectorValue, 278 } 279 280 switch testType { 281 case policyCreationTest: 282 err = nps.runPolicyCreationTest(templateMap, config) 283 case podCreationTest: 284 err = nps.runPodCreationTest(templateMap) 285 default: 286 err = fmt.Errorf("unknown testType is specified: %q", testType) 287 } 288 289 return err 290 } 291 292 func (nps *networkPolicyEnforcementMeasurement) runPodCreationTest(depTemplateMap map[string]interface{}) error { 293 klog.V(2).Infof("Starting network policy enforcement latency measurement for pod creation") 294 return nps.createTestClientDeployments(depTemplateMap, podCreationTest, depTestClientPodCreationFilePath) 295 } 296 297 func (nps *networkPolicyEnforcementMeasurement) runPolicyCreationTest(depTemplateMap map[string]interface{}, config *measurement.Config) error { 298 klog.V(2).Infof("Starting network policy enforcement latency measurement for policy creation") 299 if nps.baseline { 300 klog.Warningf("Baseline flag is specified, which is only used for pod creation test, and means that no network policies should be created. Skipping policy creation test") 301 return nil 302 } 303 304 if err := nps.createTestClientDeployments(depTemplateMap, policyCreationTest, depTestClientPolicyCreationFilePath); err != nil { 305 return err 306 } 307 308 const ( 309 timeout = 2 * time.Minute 310 waitInterval = 5 * time.Second 311 ) 312 ctx, cancel := context.WithTimeout(context.TODO(), timeout) 313 defer cancel() 314 315 desiredPodCount := len(nps.targetNamespaces) 316 options := &measurementutil.WaitForPodOptions{ 317 DesiredPodCount: func() int { return desiredPodCount }, 318 CallerName: nps.String(), 319 WaitForPodsInterval: waitInterval, 320 } 321 322 objectSelector := &util.ObjectSelector{ 323 Namespace: nps.testClientNamespace, 324 LabelSelector: fmt.Sprintf("type = %s", policyCreationTest), 325 } 326 327 podStore, err := measurementutil.NewPodStore(nps.k8sClient, objectSelector) 328 if err != nil { 329 return err 330 } 331 332 klog.V(2).Infof("Waiting for policy creation test client pods to be running") 333 _, err = measurementutil.WaitForPods(ctx, podStore, options) 334 if err != nil { 335 klog.Warningf("Not all %d policy creation test client pods are running after %v", len(nps.targetNamespaces), timeout) 336 } 337 338 wg := sync.WaitGroup{} 339 wg.Add(1) 340 // Create load policies while allow policies are being created to take network 341 // policy churn into account. 342 go func() { 343 nps.createLoadPolicies(config) 344 wg.Done() 345 }() 346 347 nps.createAllowPoliciesForPolicyCreationLatency() 348 wg.Wait() 349 350 return nil 351 } 352 353 func (nps *networkPolicyEnforcementMeasurement) createPolicyAllowAPIServer() error { 354 policyName := "allow-egress-apiserver" 355 if policy, err := nps.k8sClient.NetworkingV1().NetworkPolicies(nps.testClientNamespace).Get(context.TODO(), policyName, metav1.GetOptions{}); err == nil && policy != nil { 356 klog.Warningf("Attempting to create %q network policy, but it already exists", policyName) 357 return nil 358 } 359 360 // Get kube-apiserver IP address to allow connections to it from the test 361 // client pods. It's needed since network policies are denying connections 362 // with all endpoints that are not in the specified Labels / CIDR range. 363 endpoints, err := nps.k8sClient.CoreV1().Endpoints(corev1.NamespaceDefault).Get(context.TODO(), "kubernetes", metav1.GetOptions{}) 364 if err != nil { 365 return fmt.Errorf("failed to get kube-apiserver Endpoints object: %v", err) 366 } 367 368 if len(endpoints.Subsets) == 0 || len(endpoints.Subsets[0].Addresses) == 0 { 369 return fmt.Errorf("kube-apiserver Endpoints object does not have an IP address") 370 } 371 372 kubeAPIServerIP := endpoints.Subsets[0].Addresses[0].IP 373 templateMap := map[string]interface{}{ 374 "Name": policyName, 375 "Namespace": nps.testClientNamespace, 376 "TestClientLabel": netPolicyTestClientName, 377 "kubeAPIServerIP": kubeAPIServerIP, 378 } 379 380 if err := nps.framework.ApplyTemplatedManifests(manifestsFS, policyEgressApiserverFilePath, templateMap); err != nil { 381 return fmt.Errorf("error while creating allow egress to apiserver network policy: %v", err) 382 } 383 384 return nil 385 } 386 387 func (nps *networkPolicyEnforcementMeasurement) createPolicyToTargetPods(policyName, targetNamespace, testType string, allowForTargetPods bool) error { 388 templateMap := map[string]interface{}{ 389 "Name": policyName, 390 "Namespace": nps.testClientNamespace, 391 "TypeLabelValue": testType, 392 } 393 394 if allowForTargetPods { 395 templateMap["TargetLabelValue"] = nps.targetLabelValue 396 } else { 397 templateMap["TargetLabelValue"] = denyLabelValue 398 } 399 400 if len(targetNamespace) > 0 { 401 templateMap["OnlyTargetNamespace"] = true 402 templateMap["TargetNamespace"] = targetNamespace 403 } else { 404 templateMap["OnlyTargetNamespace"] = false 405 } 406 407 if err := nps.framework.ApplyTemplatedManifests(manifestsFS, policyEgressTargetPodsFilePath, templateMap); err != nil { 408 return fmt.Errorf("error while creating allow egress to pods network policy: %v", err) 409 } 410 411 return nil 412 } 413 414 func (nps *networkPolicyEnforcementMeasurement) createTestClientDeployments(templateMap map[string]interface{}, testType, deploymentFilePath string) error { 415 klog.V(2).Infof("Creating test client deployments for measurement %q", networkPolicyEnforcementName) 416 templateMap["TypeLabelValue"] = testType 417 418 // Create a test client deployment for each test namespace. 419 for i, ns := range nps.targetNamespaces { 420 templateMap["Name"] = fmt.Sprintf("%s-%s-%d", testType, netPolicyTestClientName, i) 421 templateMap["TargetNamespace"] = ns 422 templateMap["AllowPolicyName"] = fmt.Sprintf("%s-%d", allowPolicyName, i) 423 424 if err := nps.framework.ApplyTemplatedManifests(manifestsFS, deploymentFilePath, templateMap); err != nil { 425 return fmt.Errorf("error while creating test client deployment: %v", err) 426 } 427 } 428 429 return nil 430 } 431 432 func (nps *networkPolicyEnforcementMeasurement) createLoadPolicies(config *measurement.Config) { 433 policyLoadTargetBaseName, err := util.GetStringOrDefault(config.Params, "policyLoadTargetBaseName", defaultPolicyTargetLoadBaseName) 434 if err != nil { 435 klog.Errorf("Failed getting parameter policyLoadBaseName value, error: %v", err) 436 return 437 } 438 439 policyLoadCount, err := util.GetIntOrDefault(config.Params, "policyLoadCount", defaultPolicyLoadCount) 440 if err != nil { 441 klog.Errorf("Failed getting parameter policyLoadBaseName value, error: %v", err) 442 return 443 } 444 445 policyLoadQPS, err := util.GetIntOrDefault(config.Params, "policyLoadQPS", defaultPolicyLoadQPS) 446 if err != nil { 447 klog.Errorf("Failed getting parameter policyLoadQPS value, error: %v", err) 448 return 449 } 450 451 policiesPerNs := policyLoadCount / len(nps.targetNamespaces) 452 limiter := rate.NewLimiter(rate.Limit(policyLoadQPS), policyLoadQPS) 453 454 for nsIdx, ns := range nps.targetNamespaces { 455 octet := nsIdx % 256 456 baseCidr := fmt.Sprintf("10.0.%d.0/24", octet) 457 458 for depIdx := 0; depIdx < policiesPerNs; depIdx++ { 459 // This will be the same as "small-deployment-0".."small-deployment-50", 460 // that is used in the ClusterLoader2 load test. 461 // https://github.com/kubernetes/perf-tests/tree/master/clusterloader2/testing/load 462 podSelectorLabelValue := fmt.Sprintf("policy-load-%s-%d", policyLoadTargetBaseName, depIdx) 463 templateMapForTargetPods := map[string]interface{}{ 464 "Name": fmt.Sprintf("%s-%d", podSelectorLabelValue, nsIdx), 465 "Namespace": ns, 466 "PodSelectorLabelValue": podSelectorLabelValue, 467 "CIDR": baseCidr, 468 } 469 470 err := limiter.Wait(context.TODO()) 471 if err != nil { 472 klog.Errorf("limiter.Wait() returned an error: %v", err) 473 return 474 } 475 476 if err := nps.framework.ApplyTemplatedManifests(manifestsFS, policyLoadFilePath, templateMapForTargetPods); err != nil { 477 klog.Errorf("Error while creating load network policy for label selector 'name=%s': %v", podSelectorLabelValue, err) 478 } 479 } 480 } 481 } 482 483 func (nps *networkPolicyEnforcementMeasurement) createAllowPoliciesForPolicyCreationLatency() { 484 klog.V(2).Infof("Creating allow network policies for measurement %q", networkPolicyEnforcementName) 485 486 for i, ns := range nps.targetNamespaces { 487 policyName := fmt.Sprintf("%s-%d", allowPolicyName, i) 488 err := nps.createPolicyToTargetPods(policyName, ns, policyCreationTest, true) 489 if err != nil { 490 klog.Errorf("Failed to create a network policy to allow traffic to namespace %q", ns) 491 } 492 } 493 } 494 495 // complete deletes test client deployments for the specified test mode. 496 func (nps *networkPolicyEnforcementMeasurement) complete(config *measurement.Config) error { 497 testType, err := util.GetString(config.Params, "testType") 498 if err != nil { 499 return err 500 } 501 502 var typeLabelValue string 503 switch testType { 504 case policyCreationTest: 505 typeLabelValue = policyCreationTest 506 case podCreationTest: 507 typeLabelValue = podCreationTest 508 default: 509 return fmt.Errorf("unknown testType is specified: %q", testType) 510 } 511 512 listOpts := metav1.ListOptions{LabelSelector: fmt.Sprintf("type=%s", typeLabelValue)} 513 err = nps.k8sClient.AppsV1().Deployments(nps.testClientNamespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, listOpts) 514 if err != nil { 515 return fmt.Errorf("failed to complete %q test, error: %v", typeLabelValue, err) 516 } 517 518 return nil 519 } 520 521 func (nps *networkPolicyEnforcementMeasurement) deleteClusterRoleAndBinding() error { 522 klog.V(2).Infof("Deleting ClusterRole and ClusterRoleBinding for measurement %q", networkPolicyEnforcementName) 523 524 if err := nps.k8sClient.RbacV1().ClusterRoles().Delete(context.TODO(), netPolicyTestClientName, metav1.DeleteOptions{}); err != nil { 525 return err 526 } 527 528 return nps.k8sClient.RbacV1().ClusterRoleBindings().Delete(context.TODO(), netPolicyTestClientName, metav1.DeleteOptions{}) 529 } 530 531 func (nps *networkPolicyEnforcementMeasurement) cleanUp() error { 532 if nps.k8sClient == nil { 533 return fmt.Errorf("cleanup skipped - the measurement is not running") 534 } 535 536 if err := nps.deleteClusterRoleAndBinding(); err != nil { 537 return err 538 } 539 540 klog.V(2).Infof("Deleting namespace %q for measurement %q", nps.testClientNamespace, networkPolicyEnforcementName) 541 return nps.k8sClient.CoreV1().Namespaces().Delete(context.TODO(), nps.testClientNamespace, metav1.DeleteOptions{}) 542 } 543 544 // String returns a string representation of the measurement. 545 func (nps *networkPolicyEnforcementMeasurement) String() string { 546 return networkPolicyEnforcementName 547 } 548 549 // Dispose cleans up after the measurement. 550 func (nps *networkPolicyEnforcementMeasurement) Dispose() { 551 if err := nps.cleanUp(); err != nil { 552 klog.Errorf("Failed to clean up, error: %v", err) 553 } 554 }