From 3cbed02c00bb03da6c3666078b88d3ed18bf0bf4 Mon Sep 17 00:00:00 2001 From: Sanskarzz Date: Tue, 21 May 2024 17:11:39 +0530 Subject: [PATCH 1/4] chore: mtls demo show use of plugin with multiple service Signed-off-by: Sanskarzz --- demo/istio-mtls/README.md | 314 ++++++++++++++++++ demo/istio-mtls/arch-istio-mtls.png | Bin 0 -> 103868 bytes demo/istio-mtls/bootstrap.sh | 31 ++ .../manifests/authorizationpolicy.yaml | 16 + .../istio-mtls/manifests/ext-auth-server.yaml | 52 +++ demo/istio-mtls/manifests/namespace.yaml | 13 + .../manifests/peerAuthentication.yaml | 39 +++ demo/istio-mtls/manifests/policy-config.yaml | 48 +++ .../manifests/test-application-1.yaml | 33 ++ .../manifests/test-application-2.yaml | 33 ++ demo/istio-mtls/manifests/test-client.yaml | 19 ++ demo/istio-mtls/manifests/test-peerauth.yaml | 8 + 12 files changed, 606 insertions(+) create mode 100644 demo/istio-mtls/README.md create mode 100644 demo/istio-mtls/arch-istio-mtls.png create mode 100755 demo/istio-mtls/bootstrap.sh create mode 100644 demo/istio-mtls/manifests/authorizationpolicy.yaml create mode 100644 demo/istio-mtls/manifests/ext-auth-server.yaml create mode 100644 demo/istio-mtls/manifests/namespace.yaml create mode 100644 demo/istio-mtls/manifests/peerAuthentication.yaml create mode 100644 demo/istio-mtls/manifests/policy-config.yaml create mode 100644 demo/istio-mtls/manifests/test-application-1.yaml create mode 100644 demo/istio-mtls/manifests/test-application-2.yaml create mode 100644 demo/istio-mtls/manifests/test-client.yaml create mode 100644 demo/istio-mtls/manifests/test-peerauth.yaml diff --git a/demo/istio-mtls/README.md b/demo/istio-mtls/README.md new file mode 100644 index 0000000..2af3dbb --- /dev/null +++ b/demo/istio-mtls/README.md @@ -0,0 +1,314 @@ +# Istio mTLS demo + +This Istio mTLS Demo is a prototype of kyverno-envoy-plugin . + +## Overview And Architecture + +Istio is an open source service mesh for managing the different microservices that make up a cloud-native application. Istio provides a mechanism to use a service as an external authorizer with the [AuthorizationPolicy API](https://istio.io/latest/docs/tasks/security/authorization/authz-custom/). + +The kyverno-envoy-plugin is a custom Envoy filter that is used to intercept the incoming request to the service and validate the request using the kyverno engine. + +In this tutorial we will create a two simple microservices which are going to make external authorization to a single kyverno-envoy-plugin service in the mesh. With this tutorial we are going to understand how to use multiple microservices to make authorization decisions to a single ext-authz server. + +![arch-istio-mtls](arch-istio-mtls.png) + +To handle multiple different requests effectively, we leverage the `match/exclude` declarations to route the specific authz-request to the appropriate validating policy within the Kyverno engine. This approach allows us to execute the right validating policy for each request, enabling efficient and targeted request processing. + +### Example Policy + +The following policies will be executed by the kyverno-envoy-plugin to validate incoming requests made specifically to the `testapp-1` service. By leveraging the match declarations, we ensure that these policies are executed only when the incoming request is destined for the `testapp-1` service. This targeted approach allows us to apply the appropriate validation rules and policies based on the specific service being accessed. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test-policy +spec: + rules: + - name: deny-external-calls-testapp-1 + match: + any: + - request: + http: + host: 'testapp-1.demo.svc.cluster.local:8080' + assert: + all: + - message: "The GET method is restricted to the /book path." + check: + request: + http: + method: 'GET' + path: '/book' +``` +To execute the policy when the incoming request is made to `testapp-2` service we need to use the `match` declarations. + +```yaml +apiVersion: json.kyverno.io/v1alpha1 +kind: ValidatingPolicy +metadata: + name: test-policy +spec: + rules: + - name: deny-external-calls-testapp-2 + match: + any: + - request: + http: + host: 'testapp-2.demo.svc.cluster.local:8080' + assert: + all: + - message: "The GET method is restricted to the /movies path." + check: + request: + http: + method: 'GET' + path: '/movie' +``` +The example json request for above payload will be like below. + +```json +{ + "source": { + "address": { + "socketAddress": { + "address": "10.244.0.71", + "portValue": 33880 + } + } + }, + "destination": { + "address": { + "socketAddress": { + "address": "10.244.0.65", + "portValue": 8080 + } + } + }, + "request": { + "time": "2024-05-20T07:52:01.566887Z", + "http": { + "id": "5415544797791892902", + "method": "GET", + "headers": { + ":authority": "testapp-2.demo.svc.cluster.local:8080", + ":method": "GET", + ":path": "/movie", + ":scheme": "http", + "user-agent": "Wget", + "x-forwarded-proto": "http", + "x-request-id": "a3ad9f03-c9cd-4eab-97d1-83e90e0cee1b" + }, + "path": "/movie", + "host": "testapp-2.demo.svc.cluster.local:8080", + "scheme": "http", + "protocol": "HTTP/1.1" + } + }, + "metadataContext": {}, + "routeMetadataContext": {} +} +``` + +To enhance security, we can implement Mutual TLS (mTLS) for peer authentication between test services and kyverno-envoy-plugin. Since we are currently using JSON request data to validate incoming requests, there is a potential risk of this data being tampered with during transit. Implementing mTLS would ensure that communication between services is encrypted and authenticated, mitigating the risk of unauthorized data modification. + +```yaml +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: mtls-demo + namespace: demo +spec: + mtls: + mode: STRICT +--- +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: mtls-testapp-1 + namespace: demo +spec: + selector: + matchLabels: + app: testapp-1 + mtls: + mode: STRICT + portLevelMtls: + 8080: + mode: PERMISSIVE +--- +apiVersion: security.istio.io/v1beta1 +kind: PeerAuthentication +metadata: + name: mtls-testapp-2 + namespace: demo +spec: + selector: + matchLabels: + app: testapp-2 + mtls: + mode: STRICT + portLevelMtls: + 8080: + mode: PERMISSIVE +``` +## Demo instructions + +### Required tools + +1. [`kind`](https://kind.sigs.k8s.io/) +1. [`kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) +1. [`helm`](https://helm.sh/docs/intro/install/) + +### Create a local cluster and install Istio + +The [bootstrap.sh](bootstrap.sh) script contains everything needed to create a local cluster and install Istio. + +```console +# create a local cluster and install istio +./bootstrap.sh +``` +### Sample applications + +Manifests for the sample applications are available in [test-application-1.yaml](manifests/test-application-1.yaml) and [test-application-2.yaml](manifests/test-application-2.yaml). The sample app `testapp-1` provides information about books in a collection and exposes APIs to get, create and delete Book resources. The sample app `testapp-2` provides information about movies in a collection and exposes APIs to get, create and delete Movie resources. + +```console +# Create a namespace `demo` +kubectl apply -f ./manifests/namespace.yaml +``` + +```console +# deploy sample application testapp-1 and testapp-2 +kubectl apply -f ./manifests/test-application-1.yaml +kubectl apply -f ./manifests/test-application-1.yaml +``` +### Calling the sample applications + +We are going to call the sample applications using a pod in the cluster. + +```console +kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --output-document - testapp-1.demo.svc.cluster.local:8080/book + +[{"id":"1","bookname":"Harry Potter","author":"J.K. Rowling"},{"id":"2","bookname":"Animal Farm","author":"George Orwell"}] +pod "test" deleted + +``` +```console +kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --output-document - testapp-2.demo.svc.cluster.local:8080/movie + +[{"id":"1","Moviename":"Inception","Actor":"Leonardo DiCaprio"},{"id":"2","Moviename":"Batman","Actor":"Jack Nicholson"}] +pod "test" deleted + +``` + +### Register authorization provider + +Edit the mesh configmap to register authorization provider with the following command: + +```console +kubectl edit configmap istio -n istio-system +``` +In the editor, add the extension provider definitions to the mesh configmap. + +```yaml + data: + mesh: |- + extensionProviders: + - name: "kyverno-ext-authz-grpc" + envoyExtAuthzGrpc: + service: "ext-authz.demo.svc.cluster.local" + port: "9000" +``` + +### Authorization policy + +Now we can deploy an istio `AuthorizationPolicy`: +AuthorizationPolicy to tell Istio to use kyverno-envoy-plugin as the Authz Server + +```console +kubectl apply -f ./manifests/authorizationpolicy.yaml +``` + +```yaml +apiVersion: security.istio.io/v1 +kind: AuthorizationPolicy +metadata: + name: kyverno-ext-authz-grpc + namespace: demo +spec: + action: CUSTOM + provider: + # The provider name must match the extension provider defined in the mesh config. + name: kyverno-ext-authz-grpc + rules: + # The rules specify when to trigger the external authorizer. + - to: + - operation: + paths: ["/book","/movie"] +``` + +This policy configures an external service for authorization. Note that the service is not specified directly in the policy but using a provider.name field. The rules specify that requests to paths `/book` and `/movies`. + +### Authorization service deployment + +The deployment manifest of the authorization service is available in [ext-auth-server.yaml](manifests/ext-auth-server.yaml). This deployment require policy through configmap . + +Apply the policy configmap with the following command. + +```console +kubectl apply -f ./manifests/policy-configmap.yaml +``` +```console +#Deploy the kyverno external authorizer server +kubectl apply -f ./manifests/ext-auth-server.yaml +``` +Verify the sample external authorizer is up and running: +```console +kubectl logs "$(kubectl get pod -l app=ext-authz -n demo -o jsonpath={.items..metadata.name})" -n demo -c ext-authz -f +Starting GRPC server on Port 9000 +Starting HTTP server on Port 8000 + +``` +### Apply PeerAuthentication Policy + +Apply the PeerAuthentication policy to enable mTLS for the sample applications and external authorizer. + +```console +kubectl apply -f ./manifests/peerAuthentication.yaml +``` + +### Test the sample applications + +Check on the logs of the sample applications to see that the requests are accepted and rejected + +Check on `GET` request on `testapp-1` which is allowed according to policy `deny-external-calls-testapp-1` + +```console +kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --output-document - testapp-1.demo.svc.cluster.local:8080/book + +[{"id":"1","bookname":"Harry Potter","author":"J.K. Rowling"},{"id":"2","bookname":"Animal Farm","author":"George Orwell"}] +pod "test" deleted +``` + +Check on `GET` request on `testapp-2` which is allowed according to policy `deny-external-calls-testapp-2` + +```console +kubectl run test -it --rm --restart=Never --image=busybox -- wget -q --output-document - testapp-2.demo.svc.cluster.local:8080/movie + +[{"id":"1","Moviename":"Inception","Actor":"Leonardo DiCaprio"},{"id":"2","Moviename":"Batman","Actor":"Jack Nicholson"}] +pod "test" deleted + +``` + +Check logs of external authorizer to see that the requests are which policy was executed for a perticular request . + +```console +kubectl logs "$(kubectl get pod -l app=ext-authz -n demo -o jsonpath={.items..metadata.name})" -n demo -c ext-authz -f +Starting GRPC server on Port 9000 +Starting HTTP server on Port 8000 +2024/05/21 07:41:33 Request is initialized in kyvernojson engine . +2024/05/21 07:41:33 Request passed the deny-external-calls-testapp-1 policy rule. +2024/05/21 07:42:22 Request is initialized in kyvernojson engine . +2024/05/21 07:42:22 Request passed the deny-external-calls-testapp-2 policy rule. +``` +First request was directed to testapp-1 which was allowed by the policy `deny-external-calls-testapp-1` and the second request was directed to testapp-2 which was allowed by the policy `deny-external-calls-testapp-2`. + diff --git a/demo/istio-mtls/arch-istio-mtls.png b/demo/istio-mtls/arch-istio-mtls.png new file mode 100644 index 0000000000000000000000000000000000000000..1aaa55b6ca6a4d689dc3969ae1e405c4f8c68868 GIT binary patch literal 103868 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

a}Isf+a=UISDSDao*SfRp|*X%m; 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 literal 0 HcmV?d00001 diff --git a/demo/istio-mtls/bootstrap.sh b/demo/istio-mtls/bootstrap.sh new file mode 100755 index 0000000..5f63a84 --- /dev/null +++ b/demo/istio-mtls/bootstrap.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +KIND_IMAGE=kindest/node:v1.29.2 +ISTIO_REPO=https://istio-release.storage.googleapis.com/charts +ISTIO_NS=istio-system + +# Create Kind cluster +kind create cluster --image $KIND_IMAGE --wait 1m --config - < Date: Mon, 29 Apr 2024 13:06:53 +0530 Subject: [PATCH 2/4] chore: add performance test for kyverno-envoy-plugin Signed-off-by: Sanskarzz --- tests/performance-test/README.md | 196 ++++++++++++++++++++++ tests/performance-test/application.yaml | 211 ++++++++++++++++++++++++ tests/performance-test/k6-script.js | 43 +++++ 3 files changed, 450 insertions(+) create mode 100644 tests/performance-test/README.md create mode 100644 tests/performance-test/application.yaml create mode 100644 tests/performance-test/k6-script.js diff --git a/tests/performance-test/README.md b/tests/performance-test/README.md new file mode 100644 index 0000000..c23c4ad --- /dev/null +++ b/tests/performance-test/README.md @@ -0,0 +1,196 @@ +# Performance Testing for kyverno-envoy-plugin + +This document outlines the approach for performance testing the `kyverno-envoy-plugin`, which is a plugin for Envoy that enforces Kyverno policies on API requests. + +## Benchmark Setup + +The benchmark setup consists of the following components: + +### Sample Application + +The first component is a simple Go application that provides information of books in the library books collection and exposes APIs to `get`, `create` and `delete` books collection. Check this out for more information about the [Go test application](https://github.com/Sanskarzz/kyverno-envoy-demos/tree/main/test-application) . + +### Envoy + +The second component is the Envoy proxy, which runs alongside the example application. The Envoy configuration defines an external authorization filter `envoy.ext_authz` for a gRPC authorization server. The config uses Envoy's in-built gRPC client to make external gRPC calls. + +``` +static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 8000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + - name: envoy.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + transport_api_version: V3 + with_request_body: + max_request_bytes: 8192 + allow_partial_message: true + failure_mode_allow: false + grpc_service: + google_grpc: + target_uri: 127.0.0.1:9191 + stat_prefix: ext_authz + timeout: 0.5s + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 +admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 +layered_runtime: + layers: + - name: static_layer_0 + static_layer: + envoy: + resource_limits: + listener: + example_listener_name: + connection_limit: 10000 + overload: + global_downstream_max_connections: 50000 +``` + +### Kyverno-envoy-plugin + +The third component is the `kyverno-envoy-plugin` itself, which is configured to load and enforce Kyverno policies on incoming requests. checkout for [kyverno-envoy-plugin](application.yaml) + +## Benchmark Scenarios + +The following scenarios should be tested to compare the performance of the `kyverno-envoy-plugin` under different conditions: + +1. **App Only**: Requests are sent directly to the application, without Envoy or the `kyverno-envoy-plugin`. +2. **App and Envoy**: Envoy is included in the request path, but the `kyverno-envoy-plugin` is not (i.e., Envoy External Authorization API is disabled). +3. **App, Envoy, and Kyverno (RBAC policy)**: Envoy External Authorization API is enabled, and a sample real-world RBAC policy is loaded into the `kyverno-envoy-plugin`. + +## Load Testing with k6 + +To perform load testing, we'll use the k6 tool. Follow these steps: + +1. **Install k6**: Install k6 on your machine by following the instructions on the official website: https://k6.io/docs/getting-started/installation/ + +2. **Write the k6 script**: An example script is provided in the repository [k6-script.js](k6-script.js) + +```js +import http from 'k6/http'; +import { check, group, sleep } from 'k6'; + +export const options = { + stages: [ + { duration: '30s', target: 100 }, // Ramp-up to 100 virtual users over 30 seconds + { duration: '1m', target: 100 }, // Stay at 100 virtual users for 1 minute + { duration: '30s', target: 0 }, // Ramp-down to 0 virtual users over 30 seconds + ], +}; + +const BASE_URL = 'minikube ip with sample application'; // Replace with your application URL + +export default function () { + group('GET /book with admin token', () => { + const res = http.get(`${BASE_URL}/book`, { + headers: { 'Authorization': 'Bearer your_admin_token' }, + }); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + }); + + group('GET /book with guest token', () => { + const res = http.get(`${BASE_URL}/book`, { + headers: { 'Authorization': 'Bearer your_guest_token' }, + }); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + }); + + group('POST /book with guest token', () => { + const res = http.post(`${BASE_URL}/book`, { + headers: { 'Authorization': 'Bearer your_guest_token' }, + }); + check(res, { + 'is status 403': (r) => r.status === 403, + }); + }); + + sleep(1); // Sleep for 1 second between iterations +} +``` + +3. **Run the k6 test**: Run the load test with the following command: + +```console +k6 run k6-script.yaml +``` +4. **Analyze the results**: Generate an HTML report with detailed insight by running: + +```console +k6 run --out html=report.html k6-script.js +``` +5. ***Repeat for different scenarios**: + +- # App only + Results + ```html + + ``` + +- # App and Envoy + Results + ```html + + ``` + +- # App, Envoy and Kyverno-envoy-plugin + Results + ```html + + ``` +## Measuring Performance + +The following metrics should be measured to evaluate the performance impact of the `kyverno-envoy-plugin`: + +- End-to-end latency +- Kyverno evaluation latency +- gRPC server handler latency +- Resource utilization (CPU, memory) + + diff --git a/tests/performance-test/application.yaml b/tests/performance-test/application.yaml new file mode 100644 index 0000000..a6bf79d --- /dev/null +++ b/tests/performance-test/application.yaml @@ -0,0 +1,211 @@ +# Application Deployment with kyverno-envoy-plugin and Envoy sidecars. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testapp + namespace: demo +spec: + replicas: 1 + selector: + matchLabels: + app: testapp + template: + metadata: + labels: + app: testapp + spec: + initContainers: + - name: proxy-init + image: sanskardevops/proxyinit:latest + # Configure the iptables bootstrap script to redirect traffic to the + # Envoy proxy on port 8000, specify that Envoy will be running as user + # 1111, These values must match up with the configuration + # defined below for the "envoy" and "kyverno-envoy-plugin" containers. + args: ["-p", "7000", "-u", "1111"] + securityContext: + capabilities: + add: + - NET_ADMIN + runAsNonRoot: false + runAsUser: 0 + containers: + - name: test-application + image: sanskardevops/test-application:0.0.1 + ports: + - containerPort: 8080 + - name: envoy + image: envoyproxy/envoy:v1.30-latest + securityContext: + runAsUser: 1111 + imagePullPolicy: IfNotPresent + volumeMounts: + - readOnly: true + mountPath: /config + name: proxy-config + args: + - "envoy" + - "--config-path" + - "/config/envoy.yaml" + - name: kyverno-envoy-plugin + image: sanskardevops/plugin:0.0.25 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8000 + - containerPort: 9000 + volumeMounts: + - readOnly: true + mountPath: /policies + name: policy-files + args: + - "serve" + - "--policy=/policies/policy.yaml" + volumes: + - name: proxy-config + configMap: + name: proxy-config + - name: policy-files + configMap: + name: policy-files +--- +# Envoy Config with External Authorization filter that will query kyverno-envoy-plugin. +apiVersion: v1 +kind: ConfigMap +metadata: + name: proxy-config + namespace: demo +data: + envoy.yaml: | + static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 7000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + - name: envoy.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + transport_api_version: V3 + with_request_body: + max_request_bytes: 8192 + allow_partial_message: true + failure_mode_allow: false + grpc_service: + google_grpc: + target_uri: 127.0.0.1:9000 + stat_prefix: ext_authz + timeout: 0.5s + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 + layered_runtime: + layers: + - name: static_layer_0 + static_layer: + envoy: + resource_limits: + listener: + example_listener_name: + connection_limit: 10000 + overload: + global_downstream_max_connections: 50000 +--- +# Example policy to enforce into kyverno-envoy-plugin sidecars. +apiVersion: v1 +kind: ConfigMap +metadata: + name: policy-files + namespace: demo +data: + policy.yaml: | + apiVersion: json.kyverno.io/v1alpha1 + kind: ValidatingPolicy + metadata: + name: checkrequest + spec: + rules: + - name: deny-guest-request-at-post + assert: + any: + - message: "POST method calls at path /book are not allowed to guests users" + check: + request: + http: + method: POST + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): admin + path: /book + - message: "GET method call is allowed to both guest and admin users" + check: + request: + http: + method: GET + headers: + authorization: + (split(@, ' ')[1]): + (jwt_decode(@ , 'secret').payload.role): guest + path: /book +--- +### Service to expose sample application +apiVersion: v1 +kind: Service +metadata: + name: testapp + namespace: demo +spec: + type: ClusterIP + selector: + app: testapp + ports: + - port: 8080 + targetPort: 8080 diff --git a/tests/performance-test/k6-script.js b/tests/performance-test/k6-script.js new file mode 100644 index 0000000..0c9c579 --- /dev/null +++ b/tests/performance-test/k6-script.js @@ -0,0 +1,43 @@ +import http from 'k6/http'; +import { check, group, sleep } from 'k6'; + +export const options = { + stages: [ + { duration: '30s', target: 100 }, // Ramp-up to 100 virtual users over 30 seconds + { duration: '1m', target: 100 }, // Stay at 100 virtual users for 1 minute + { duration: '30s', target: 0 }, // Ramp-down to 0 virtual users over 30 seconds + ], +}; + +const BASE_URL = 'minikube ip with sample application'; // Replace with your application URL + +export default function () { + group('GET /book with admin token', () => { + const res = http.get(`${BASE_URL}/book`, { + headers: { 'Authorization': 'Bearer your_admin_token' }, + }); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + }); + + group('GET /book with guest token', () => { + const res = http.get(`${BASE_URL}/book`, { + headers: { 'Authorization': 'Bearer your_guest_token' }, + }); + check(res, { + 'is status 200': (r) => r.status === 200, + }); + }); + + group('POST /book with guest token', () => { + const res = http.post(`${BASE_URL}/book`, { + headers: { 'Authorization': 'Bearer your_guest_token' }, + }); + check(res, { + 'is status 403': (r) => r.status === 403, + }); + }); + + sleep(1); // Sleep for 1 second between iterations +} \ No newline at end of file From 7214090e78d48de86bd337a058c34526b607b126 Mon Sep 17 00:00:00 2001 From: Sanskarzz Date: Wed, 1 May 2024 20:39:42 +0530 Subject: [PATCH 3/4] added k6 test results Signed-off-by: Sanskarzz --- tests/performance-test/README.md | 212 +++++++++++++++--- tests/performance-test/k6-script.js | 36 ++- .../app-envoy-plugin.yaml} | 2 +- .../performance-test/manifest/app-envoy.yaml | 148 ++++++++++++ tests/performance-test/manifest/app.yaml | 35 +++ 5 files changed, 377 insertions(+), 56 deletions(-) rename tests/performance-test/{application.yaml => manifest/app-envoy-plugin.yaml} (99%) create mode 100644 tests/performance-test/manifest/app-envoy.yaml create mode 100644 tests/performance-test/manifest/app.yaml diff --git a/tests/performance-test/README.md b/tests/performance-test/README.md index c23c4ad..d4e9e60 100644 --- a/tests/performance-test/README.md +++ b/tests/performance-test/README.md @@ -91,7 +91,7 @@ layered_runtime: ### Kyverno-envoy-plugin -The third component is the `kyverno-envoy-plugin` itself, which is configured to load and enforce Kyverno policies on incoming requests. checkout for [kyverno-envoy-plugin](application.yaml) +The third component is the `kyverno-envoy-plugin` itself, which is configured to load and enforce Kyverno policies on incoming requests. checkout for [kyverno-envoy-plugin](././manifest/app-envoy-plugin.yaml) ## Benchmark Scenarios @@ -121,36 +121,28 @@ export const options = { ], }; -const BASE_URL = 'minikube ip with sample application'; // Replace with your application URL +/* +Replace ip for every scenerio +export SERVICE_PORT=$(kubectl -n demo get service testapp -o jsonpath='{.spec.ports[?(@.port==8080)].nodePort}') +export SERVICE_HOST=$(minikube ip) +export SERVICE_URL=$SERVICE_HOST:$SERVICE_PORT +echo $SERVICE_URL -export default function () { - group('GET /book with admin token', () => { - const res = http.get(`${BASE_URL}/book`, { - headers: { 'Authorization': 'Bearer your_admin_token' }, - }); - check(res, { - 'is status 200': (r) => r.status === 200, - }); - }); +http://192.168.49.2:31541 +*/ +const BASE_URL = 'http://192.168.49.2:31541'; + +export default function () { group('GET /book with guest token', () => { const res = http.get(`${BASE_URL}/book`, { - headers: { 'Authorization': 'Bearer your_guest_token' }, + headers: { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk' }, }); check(res, { 'is status 200': (r) => r.status === 200, }); }); - group('POST /book with guest token', () => { - const res = http.post(`${BASE_URL}/book`, { - headers: { 'Authorization': 'Bearer your_guest_token' }, - }); - check(res, { - 'is status 403': (r) => r.status === 403, - }); - }); - sleep(1); // Sleep for 1 second between iterations } ``` @@ -163,34 +155,186 @@ k6 run k6-script.yaml 4. **Analyze the results**: Generate an HTML report with detailed insight by running: ```console -k6 run --out html=report.html k6-script.js +k6 run --out html=report.json k6-script.js ``` 5. ***Repeat for different scenarios**: -- # App only - Results - ```html - +- # App only + In this case , request are sent directly to the sample application ie no Envoy and Kyverno-plugin in the request path . + For this run this command to apply the sample applicaition and then test with k6 + ```console + kubectl apply -f ./manifest/app.yaml + ``` + Results of the k6 when only application is applied + ```bash + + /\ |‾‾| /‾‾/ /‾‾/ + /\ / \ | |/ / / / + / \/ \ | ( / ‾‾\ + / \ | |\ \ | (‾) | + / __________ \ |__| \__\ \_____/ .io + + execution: local + script: k6-script.js + output: - + + scenarios: (100.00%) 1 scenario, 100 max VUs, 2m30s max duration (incl. graceful stop): + * default: Up to 100 looping VUs for 2m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s) + + + █ GET /book with guest token + + ✓ is status 200 + + checks.........................: 100.00% ✓ 9048 ✗ 0 + data_received..................: 2.1 MB 18 kB/s + data_sent......................: 2.6 MB 21 kB/s + group_duration.................: avg=1.01ms min=166.46µs med=775.01µs max=36ms p(90)=1.72ms p(95)=2.31ms + http_req_blocked...............: avg=15.08µs min=1.55µs med=6.54µs max=4.09ms p(90)=12.07µs p(95)=15.25µs + http_req_connecting............: avg=4.58µs min=0s med=0s max=1.57ms p(90)=0s p(95)=0s + http_req_duration..............: avg=745.73µs min=103.06µs med=549.17µs max=35.88ms p(90)=1.26ms p(95)=1.75ms + { expected_response:true }...: avg=745.73µs min=103.06µs med=549.17µs max=35.88ms p(90)=1.26ms p(95)=1.75ms + http_req_failed................: 0.00% ✓ 0 ✗ 9048 + http_req_receiving.............: avg=119.69µs min=11.33µs med=77.78µs max=10.97ms p(90)=193.73µs p(95)=285.58µs + http_req_sending...............: avg=41µs min=6.96µs med=31.12µs max=2.39ms p(90)=61.88µs p(95)=78.15µs + http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s + http_req_waiting...............: avg=585.04µs min=75.52µs med=407.87µs max=35.84ms p(90)=965.49µs p(95)=1.33ms + http_reqs......................: 9048 75.050438/s + iteration_duration.............: avg=1s min=1s med=1s max=1.06s p(90)=1s p(95)=1s + iterations.....................: 9048 75.050438/s + vus............................: 2 min=2 max=100 + vus_max........................: 100 min=100 max=100 + + +running (2m00.6s), 000/100 VUs, 9048 complete and 0 interrupted iterations +default ✓ [======================================] 000/100 VUs 2m0s ``` - # App and Envoy - Results - ```html + In this case, Kyverno-envoy-plugin is not included in the path but Envoy is but Envoy External Authorization API disabled + For this run this command to apply the sample application with envoy. + ```console + kubectl apply -f ./manifest/app-envoy.yaml + ``` + Results of k6 after applying sample-application with envoy. + ```bash + + /\ |‾‾| /‾‾/ /‾‾/ + /\ / \ | |/ / / / + / \/ \ | ( / ‾‾\ + / \ | |\ \ | (‾) | + / __________ \ |__| \__\ \_____/ .io + + execution: local + script: k6-script.js + output: - + + scenarios: (100.00%) 1 scenario, 100 max VUs, 2m30s max duration (incl. graceful stop): + * default: Up to 100 looping VUs for 2m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s) + + + █ GET /book with guest token + + ✓ is status 200 + + checks.........................: 100.00% ✓ 9031 ✗ 0 + data_received..................: 2.5 MB 21 kB/s + data_sent......................: 2.6 MB 21 kB/s + group_duration.................: avg=2.66ms min=457.22µs med=1.8ms max=65.53ms p(90)=4.85ms p(95)=6.58ms + http_req_blocked...............: avg=12.81µs min=1.52µs med=5.98µs max=2.41ms p(90)=11.84µs p(95)=13.9µs + http_req_connecting............: avg=3.82µs min=0s med=0s max=2.34ms p(90)=0s p(95)=0s + http_req_duration..............: avg=2.38ms min=383.7µs med=1.58ms max=65.22ms p(90)=4.36ms p(95)=5.92ms + { expected_response:true }...: avg=2.38ms min=383.7µs med=1.58ms max=65.22ms p(90)=4.36ms p(95)=5.92ms + http_req_failed................: 0.00% ✓ 0 ✗ 9031 + http_req_receiving.............: avg=136.3µs min=12.53µs med=76.74µs max=12.75ms p(90)=183.23µs p(95)=272.91µs + http_req_sending...............: avg=41.54µs min=6.58µs med=28.1µs max=4.15ms p(90)=59.62µs p(95)=74.85µs + http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s + http_req_waiting...............: avg=2.2ms min=349.23µs med=1.43ms max=65.08ms p(90)=4.05ms p(95)=5.52ms + http_reqs......................: 9031 74.825497/s + iteration_duration.............: avg=1s min=1s med=1s max=1.06s p(90)=1s p(95)=1s + iterations.....................: 9031 74.825497/s + vus............................: 3 min=3 max=100 + vus_max........................: 100 min=100 max=100 + + +running (2m00.7s), 000/100 VUs, 9031 complete and 0 interrupted iterations +default ✓ [======================================] 000/100 VUs 2m0s ``` - # App, Envoy and Kyverno-envoy-plugin - Results - ```html + In this case, performance measurements are observed with Envoy External Authorization API enabled and a sample real-world RBAC policy loaded into kyverno-envoy-plugin . + For this apply this command to apply sample-application, envoy and kyverno-envoy-plugin + ```console + kubectl apply -f ./manifest/app-envoy-plugin.yaml + ``` + + Results of k6 after applying sample-application, Envoy and kyverno-envoy-plugin . + ```bash + + /\ |‾‾| /‾‾/ /‾‾/ + /\ / \ | |/ / / / + / \/ \ | ( / ‾‾\ + / \ | |\ \ | (‾) | + / __________ \ |__| \__\ \_____/ .io + + execution: local + script: k6-script.js + output: - + + scenarios: (100.00%) 1 scenario, 100 max VUs, 2m30s max duration (incl. graceful stop): + * default: Up to 100 looping VUs for 2m0s over 3 stages (gracefulRampDown: 30s, gracefulStop: 30s) + + + █ GET /book with guest token + + ✓ is status 200 + + checks.........................: 100.00% ✓ 8655 ✗ 0 + data_received..................: 2.4 MB 20 kB/s + data_sent......................: 2.4 MB 20 kB/s + group_duration.................: avg=46.54ms min=4.59ms med=29.69ms max=337.79ms p(90)=109.35ms p(95)=140.51ms + http_req_blocked...............: avg=11.88µs min=1.21µs med=4.15µs max=2.83ms p(90)=9.87µs p(95)=11.4µs + http_req_connecting............: avg=4.98µs min=0s med=0s max=2.18ms p(90)=0s p(95)=0s + http_req_duration..............: avg=46.37ms min=4.49ms med=29.49ms max=337.69ms p(90)=109.26ms p(95)=140.28ms + { expected_response:true }...: avg=46.37ms min=4.49ms med=29.49ms max=337.69ms p(90)=109.26ms p(95)=140.28ms + http_req_failed................: 0.00% ✓ 0 ✗ 8655 + http_req_receiving.............: avg=65.19µs min=11.14µs med=56.47µs max=5.58ms p(90)=102.86µs p(95)=145.19µs + http_req_sending...............: avg=30.35µs min=5.43µs med=18.48µs max=5.29ms p(90)=46.63µs p(95)=58µs + http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s + http_req_waiting...............: avg=46.27ms min=4.43ms med=29.42ms max=337.65ms p(90)=109.22ms p(95)=140.24ms + http_reqs......................: 8655 71.999297/s + iteration_duration.............: avg=1.04s min=1s med=1.03s max=1.33s p(90)=1.11s p(95)=1.14s + iterations.....................: 8655 71.999297/s + vus............................: 2 min=2 max=100 + vus_max........................: 100 min=100 max=100 + + +running (2m00.2s), 000/100 VUs, 8655 complete and 0 interrupted iterations +default ✓ [======================================] 000/100 VUs 2m0s ``` ## Measuring Performance The following metrics should be measured to evaluate the performance impact of the `kyverno-envoy-plugin`: -- End-to-end latency -- Kyverno evaluation latency -- gRPC server handler latency -- Resource utilization (CPU, memory) +- **End-to-end latency** + The end-to-end latency represents the time taken for a request to complete, from the client sending the request to receiving the response. Based on the k6 results, the average end-to-end latency for the different scenarios is as follows: + + - App Only: `avg=1.01ms` (from `group_duration` or `http_req_duration`) + - App and Envoy: `avg=2.38ms` (from `http_req_duration`) + - App, Envoy, and Kyverno-envoy-plugin: `avg=46.37ms` (from `http_req_duration`) + +- **Kyverno evaluation latency** + The Kyverno evaluation latency represents the time taken by the kyverno-envoy-plugin to evaluate the request against the configured policies. While the k6 results do not directly provide this metric, an estimate can be inferred by analyzing the differences in latency between the "App and Envoy" scenario and the "App, Envoy, and Kyverno-envoy-plugin" scenario. + + The difference in average latency between these two scenarios is: + `46.37ms` - `2.38ms` = `43.99ms` + + This difference can be attributed to the Kyverno evaluation latency and the gRPC server handler latency combined. Assuming the gRPC server handler latency is relatively small compared to the Kyverno evaluation latency, the estimated range for the Kyverno evaluation latency is around 40ms to 45ms. + + + + diff --git a/tests/performance-test/k6-script.js b/tests/performance-test/k6-script.js index 0c9c579..0dee9cd 100644 --- a/tests/performance-test/k6-script.js +++ b/tests/performance-test/k6-script.js @@ -9,35 +9,29 @@ export const options = { ], }; -const BASE_URL = 'minikube ip with sample application'; // Replace with your application URL +/* +Replace ip for every scenerio generate the URL with these commands -export default function () { - group('GET /book with admin token', () => { - const res = http.get(`${BASE_URL}/book`, { - headers: { 'Authorization': 'Bearer your_admin_token' }, - }); - check(res, { - 'is status 200': (r) => r.status === 200, - }); - }); +echo SERVICE_PORT=$(kubectl -n demo get service testapp -o jsonpath='{.spec.ports[?(@.port==8080)].nodePort}') +echo SERVICE_HOST=$(minikube ip) +echo SERVICE_URL=$SERVICE_HOST:$SERVICE_PORT +echo $SERVICE_URL + +http://192.168.49.2:31541 +*/ + +const BASE_URL = 'http://192.168.49.2:31700'; // Replace with your application URL + +export default function () { group('GET /book with guest token', () => { const res = http.get(`${BASE_URL}/book`, { - headers: { 'Authorization': 'Bearer your_guest_token' }, + headers: { 'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIyNDEwODE1MzksIm5iZiI6MTUxNDg1MTEzOSwicm9sZSI6Imd1ZXN0Iiwic3ViIjoiWVd4cFkyVT0ifQ.ja1bgvIt47393ba_WbSBm35NrUhdxM4mOVQN8iXz8lk' }, }); check(res, { 'is status 200': (r) => r.status === 200, }); }); - group('POST /book with guest token', () => { - const res = http.post(`${BASE_URL}/book`, { - headers: { 'Authorization': 'Bearer your_guest_token' }, - }); - check(res, { - 'is status 403': (r) => r.status === 403, - }); - }); - sleep(1); // Sleep for 1 second between iterations -} \ No newline at end of file +} diff --git a/tests/performance-test/application.yaml b/tests/performance-test/manifest/app-envoy-plugin.yaml similarity index 99% rename from tests/performance-test/application.yaml rename to tests/performance-test/manifest/app-envoy-plugin.yaml index a6bf79d..e3c5d3a 100644 --- a/tests/performance-test/application.yaml +++ b/tests/performance-test/manifest/app-envoy-plugin.yaml @@ -203,7 +203,7 @@ metadata: name: testapp namespace: demo spec: - type: ClusterIP + type: NodePort selector: app: testapp ports: diff --git a/tests/performance-test/manifest/app-envoy.yaml b/tests/performance-test/manifest/app-envoy.yaml new file mode 100644 index 0000000..fb321c6 --- /dev/null +++ b/tests/performance-test/manifest/app-envoy.yaml @@ -0,0 +1,148 @@ +# Application Deployment with kyverno-envoy-plugin and Envoy sidecars. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testapp + namespace: demo +spec: + replicas: 1 + selector: + matchLabels: + app: testapp + template: + metadata: + labels: + app: testapp + spec: + initContainers: + - name: proxy-init + image: sanskardevops/proxyinit:latest + # Configure the iptables bootstrap script to redirect traffic to the + # Envoy proxy on port 8000, specify that Envoy will be running as user + # 1111, These values must match up with the configuration + # defined below for the "envoy" and "kyverno-envoy-plugin" containers. + args: ["-p", "7000", "-u", "1111"] + securityContext: + capabilities: + add: + - NET_ADMIN + runAsNonRoot: false + runAsUser: 0 + containers: + - name: test-application + image: sanskardevops/test-application:0.0.1 + ports: + - containerPort: 8080 + - name: envoy + image: envoyproxy/envoy:v1.30-latest + securityContext: + runAsUser: 1111 + imagePullPolicy: IfNotPresent + volumeMounts: + - readOnly: true + mountPath: /config + name: proxy-config + args: + - "envoy" + - "--config-path" + - "/config/envoy.yaml" + volumes: + - name: proxy-config + configMap: + name: proxy-config +--- +# Envoy Config with disabled External Authorization API +# setting up 'failure_mode_allow: true' this will tell envoy to all requests to pass through the external authorization filter +apiVersion: v1 +kind: ConfigMap +metadata: + name: proxy-config + namespace: demo +data: + envoy.yaml: | + static_resources: + listeners: + - address: + socket_address: + address: 0.0.0.0 + port_value: 7000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + codec_type: auto + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: backend + domains: + - "*" + routes: + - match: + prefix: "/" + route: + cluster: service + http_filters: + - name: envoy.ext_authz + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz + transport_api_version: V3 + with_request_body: + max_request_bytes: 8192 + allow_partial_message: true + failure_mode_allow: true + grpc_service: + google_grpc: + target_uri: 127.0.0.1:9000 + stat_prefix: ext_authz + timeout: 0.5s + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: service + connect_timeout: 0.25s + type: strict_dns + lb_policy: round_robin + load_assignment: + cluster_name: service + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: 127.0.0.1 + port_value: 8080 + admin: + access_log_path: "/dev/null" + address: + socket_address: + address: 0.0.0.0 + port_value: 8001 + layered_runtime: + layers: + - name: static_layer_0 + static_layer: + envoy: + resource_limits: + listener: + example_listener_name: + connection_limit: 10000 + overload: + global_downstream_max_connections: 50000 +--- +### Service to expose sample application +apiVersion: v1 +kind: Service +metadata: + name: testapp + namespace: demo +spec: + type: NodePort + selector: + app: testapp + ports: + - port: 8080 + targetPort: 8080 diff --git a/tests/performance-test/manifest/app.yaml b/tests/performance-test/manifest/app.yaml new file mode 100644 index 0000000..44eebc8 --- /dev/null +++ b/tests/performance-test/manifest/app.yaml @@ -0,0 +1,35 @@ +# Application Deployment with kyverno-envoy-plugin and Envoy sidecars. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: testapp + namespace: demo +spec: + replicas: 1 + selector: + matchLabels: + app: testapp + template: + metadata: + labels: + app: testapp + spec: + containers: + - name: test-application + image: sanskardevops/test-application:0.0.1 + ports: + - containerPort: 8080 +--- +### Service to expose sample application +apiVersion: v1 +kind: Service +metadata: + name: testapp + namespace: demo +spec: + type: NodePort + selector: + app: testapp + ports: + - port: 8080 + targetPort: 8080 From 19133138b72187e7ba8aac7d38425b6a3344687b Mon Sep 17 00:00:00 2001 From: Sanskarzz Date: Mon, 6 May 2024 00:50:56 +0530 Subject: [PATCH 4/4] added resource utilization used kubectl top command Signed-off-by: Sanskarzz --- tests/performance-test/README.md | 56 ++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/tests/performance-test/README.md b/tests/performance-test/README.md index d4e9e60..fb05adf 100644 --- a/tests/performance-test/README.md +++ b/tests/performance-test/README.md @@ -152,10 +152,10 @@ export default function () { ```console k6 run k6-script.yaml ``` -4. **Analyze the results**: Generate an HTML report with detailed insight by running: +4. **Analyze the results**: Generate an json report with detailed insight by running: ```console -k6 run --out html=report.json k6-script.js +k6 run --out json=report.json k6-script.js ``` 5. ***Repeat for different scenarios**: @@ -333,8 +333,60 @@ The following metrics should be measured to evaluate the performance impact of t This difference can be attributed to the Kyverno evaluation latency and the gRPC server handler latency combined. Assuming the gRPC server handler latency is relatively small compared to the Kyverno evaluation latency, the estimated range for the Kyverno evaluation latency is around 40ms to 45ms. +- **Resource utilization** + Refers to CPU and memory usage of the Kyverno-Envoy-Plugin container , `kubectl top` utility can be laveraged to measure the resource utilization. + Get the resource utilization of the kyverno-envoy-plugin container using the following command: + ```bash + kubectl top pod -n demo --containers + ``` + To monitor resource utilization overtime use the following command: + ```bash + watch -n 1 "kubectl top pod -n demo --containers" + ``` + Now run the k6 script in different terminal window and observe the resource utilization of the kyverno-envoy-plugin container. + Initial resource utilization of the kyverno-envoy-plugin container: + ``` + POD NAME CPU(cores) MEMORY(bytes) + testapp-5955cd6f8b-dbvgd envoy 4m 70Mi + testapp-5955cd6f8b-dbvgd kyverno-envoy-plugin 1m 51Mi + testapp-5955cd6f8b-dbvgd test-application 1m 11Mi + ``` + Resource utilization of the kyverno-envoy-plugin container after 100 requests: + ``` + POD NAME CPU(cores) MEMORY(bytes) + testapp-5955cd6f8b-dbvgd envoy 110m 70Mi + testapp-5955cd6f8b-dbvgd kyverno-envoy-plugin 895m 60Mi + testapp-5955cd6f8b-dbvgd test-application 17m 15Mi + ``` + + Observations: + + - The CPU utilization of the kyverno-envoy-plugin container increased significantly from 1m to 895m after receiving 100 requests during the load test. + - The memory utilization also increased, but to a lesser extent, from 51Mi to 60Mi. + + Resource utilization of the kyverno-envoy-plugin container after load completion: + + ``` + POD NAME CPU(cores) MEMORY(bytes) + testapp-5955cd6f8b-dbvgd envoy 4m 70Mi + testapp-5955cd6f8b-dbvgd kyverno-envoy-plugin 1m 51Mi + testapp-5955cd6f8b-dbvgd test-application 1m 11Mi + ``` + + Observations: + - After the load test completed and the request volume returned to normal levels, the CPU and memory utilization of the kyverno-envoy-plugin container returned to their initial values. This indicates that the kyverno-envoy-plugin can efficiently handle the increased load during the test and release the additional resources when the load subsides. + + Correlation with k6 results: + - The k6 script simulated a load test scenario with 100 virtual users, ramping up over 30 seconds, staying at 100 users for 1 minute, and then ramping down over 30 seconds. + - During the load test, when the request volume was at its peak (100 virtual users), the kyverno-envoy-plugin container experienced a significant increase in CPU utilization, reaching 895m. + - This CPU utilization spike aligns with the increased processing demand on the kyverno-envoy-plugin to evaluate the incoming requests against the configured Kyverno policies. + - The memory utilization increase during the load test was relatively modest, suggesting that the policy evaluation did not significantly impact the memory requirements of the kyverno-envoy-plugin. + + + +