-
Notifications
You must be signed in to change notification settings - Fork 518
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PoC benchmark to track speed of k8s HPA reaction
PiperOrigin-RevId: 705191661
- Loading branch information
1 parent
7e263a0
commit 4519e28
Showing
12 changed files
with
537 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
88 changes: 88 additions & 0 deletions
88
perfkitbenchmarker/data/container/kubernetes_hpa/fib.yaml.j2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
apiVersion: v1 | ||
kind: Namespace | ||
metadata: | ||
name: fib | ||
--- | ||
apiVersion: apps/v1 | ||
kind: Deployment | ||
metadata: | ||
name: fib | ||
namespace: fib | ||
spec: | ||
selector: | ||
matchLabels: | ||
app: "fib" | ||
template: | ||
metadata: | ||
labels: | ||
app: "fib" | ||
spec: | ||
containers: | ||
- name: "fib" | ||
image: {{ fib_image }} | ||
imagePullPolicy: "Always" | ||
resources: | ||
requests: | ||
cpu: "1000m" | ||
memory: "128Mi" | ||
limits: | ||
cpu: "2000m" | ||
memory: "128Mi" | ||
ports: | ||
- containerPort: 5000 | ||
name: "web" | ||
protocol: "TCP" | ||
--- | ||
apiVersion: autoscaling/v2 | ||
kind: HorizontalPodAutoscaler | ||
metadata: | ||
name: "fib" | ||
namespace: "fib" | ||
spec: | ||
scaleTargetRef: | ||
apiVersion: "apps/v1" | ||
kind: "Deployment" | ||
name: "fib" | ||
minReplicas: 5 | ||
maxReplicas: 250 | ||
metrics: | ||
- type: "Resource" | ||
resource: | ||
name: "cpu" | ||
target: | ||
type: "Utilization" | ||
averageUtilization: 70 | ||
behavior: | ||
scaleDown: | ||
stabilizationWindowSeconds: 60 | ||
policies: | ||
- periodSeconds: 15 | ||
type: "Percent" | ||
value: 100 | ||
selectPolicy: "Min" | ||
scaleUp: | ||
stabilizationWindowSeconds: 0 | ||
policies: | ||
- periodSeconds: 15 | ||
type: "Percent" | ||
value: 100 | ||
- periodSeconds: 15 | ||
type: "Pods" | ||
value: 1000 | ||
selectPolicy: "Max" | ||
--- | ||
apiVersion: v1 | ||
kind: Service | ||
metadata: | ||
name: "fib" | ||
namespace: "fib" | ||
spec: | ||
selector: | ||
app: "fib" | ||
type: LoadBalancer | ||
externalTrafficPolicy: Cluster | ||
ports: | ||
- name: "tcp-port" | ||
protocol: "TCP" | ||
port: 5000 | ||
targetPort: 5000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# Some combinations of python 3.13/C++17 cause build failures in pandas: | ||
# https://github.com/cython/cython/issues/5790 | ||
# Avoid it by just picking 3.12. | ||
FROM --platform=linux/amd64 python:3.12 as build | ||
|
||
WORKDIR / | ||
COPY requirements.txt requirements.txt | ||
RUN pip install -r requirements.txt | ||
EXPOSE 5000 | ||
COPY . . | ||
ENTRYPOINT [ "./entrypoint.sh" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#!/bin/sh | ||
gunicorn perf_server:app -w 4 --threads 2 --bind 0.0.0.0:5000 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
"""Toy flask app to inefficiently calculate Fibonacci numbers.""" | ||
|
||
import socket | ||
import time | ||
from flask import Flask | ||
|
||
app = Flask(__name__) | ||
hostname = socket.gethostname() | ||
|
||
|
||
def calculate_fibonacci(n): | ||
"""Returns the nth Fibonacci number (inefficient for the sake of CPU load). | ||
Args: | ||
n: nth Fibonacci number to be calculated. | ||
""" | ||
if n <= 1: | ||
return n | ||
else: | ||
return calculate_fibonacci(n - 1) + calculate_fibonacci(n - 2) | ||
|
||
|
||
@app.route('/calculate') | ||
def do_calculation(): | ||
start_time = time.time() | ||
result = calculate_fibonacci(30) # Adjust the Fibonacci number for load | ||
end_time = time.time() | ||
|
||
return [{ | ||
'result': result, | ||
'calculation_time': end_time - start_time, | ||
'timestamp': start_time, | ||
'pod_id': hostname, | ||
}] | ||
|
||
|
||
if __name__ == '__main__': | ||
app.run(debug=True, host='0.0.0.0', port=5000) | ||
Check failure Code scanning / CodeQL Flask app is run in debug mode High
A Flask app appears to be run in debug mode. This may allow an attacker to run arbitrary code through the debugger.
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
blinker==1.7.0 | ||
click==8.1.7 | ||
Flask==3.0.2 | ||
gunicorn==21.2.0 | ||
itsdangerous==2.1.2 | ||
Jinja2==3.1.3 | ||
MarkupSafe==2.1.5 | ||
numpy==1.26.4 | ||
packaging==23.2 | ||
pandas==2.2.1 | ||
python-dateutil==2.9.0.post0 | ||
pytz==2024.1 | ||
six==1.16.0 | ||
tzdata==2024.1 | ||
Werkzeug==3.0.1 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
"""Locust file to simulate a "stepped" rampup of load.""" | ||
|
||
import locust | ||
|
||
|
||
class Rampup(locust.HttpUser): | ||
# Send 1QPS (per user) | ||
wait_time = locust.constant_throughput(1) | ||
|
||
@locust.task | ||
def rampup(self): | ||
# Close the connection after each request (or else users won't get load | ||
# balanced to new pods.) | ||
headers = {"Connection": "close"} | ||
|
||
self.client.get("/calculate", headers=headers) | ||
|
||
|
||
class StagesShape(locust.LoadTestShape): | ||
"""Locust LoadTestShape to simulate a "stepped" rampup.""" | ||
|
||
# pyformat: disable | ||
# pylint: disable=bad-whitespace | ||
_stages = [ | ||
{"endtime": 60, "users": 1}, # 1 rps for 1m | ||
{"endtime": 360, "users": 20}, # 20 rps for 5m | ||
{"endtime": 420, "users": 40}, # 40 rps for 1m | ||
{"endtime": 480, "users": 60}, # 60 rps for 1m | ||
{"endtime": 540, "users": 90}, # 90 rps for 1m | ||
{"endtime": 660, "users": 120}, # 120 rps for 2m | ||
{"endtime": 780, "users": 150}, # 150 rps for 2m | ||
{"endtime": 900, "users": 1}, # 1 rps for 2m | ||
# -------------- | ||
# Total: 15m | ||
] | ||
# pyformat: enable | ||
|
||
def tick(self): | ||
run_time = self.get_run_time() | ||
|
||
for stage in self._stages: | ||
if run_time < stage["endtime"]: | ||
user_count = stage["users"] | ||
spawn_rate = 100 # spawn all new users roughly immediately (over 1s) | ||
return (user_count, spawn_rate) | ||
|
||
return None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
"""Locust file to flood the SUT.""" | ||
|
||
from locust import HttpUser | ||
from locust import task | ||
|
||
|
||
class Simple(HttpUser): | ||
|
||
@task | ||
def simple(self): | ||
# Close the connection after each request (or else users won't get load | ||
# balanced to new pods.) | ||
headers = {"Connection": "close"} | ||
|
||
self.client.get("/calculate", headers=headers) |
141 changes: 141 additions & 0 deletions
141
perfkitbenchmarker/linux_benchmarks/kubernetes_hpa_benchmark.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
# Copyright 2019 PerfKitBenchmarker Authors. All rights reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
"""Runs a locust based hpa benchmark on a k8s cluster.""" | ||
|
||
import functools | ||
from typing import Any, Dict, List | ||
|
||
from absl import flags | ||
from perfkitbenchmarker import background_tasks | ||
from perfkitbenchmarker import benchmark_spec as bm_spec | ||
from perfkitbenchmarker import configs | ||
from perfkitbenchmarker import container_service | ||
from perfkitbenchmarker.linux_packages import locust | ||
from perfkitbenchmarker.sample import Sample | ||
|
||
FLAGS = flags.FLAGS | ||
|
||
flags.DEFINE_string( | ||
'kubernetes_hpa_runtime_class_name', | ||
None, | ||
'A custom runtimeClassName to apply to the pods.', | ||
) | ||
|
||
BENCHMARK_NAME = 'kubernetes_hpa' | ||
BENCHMARK_CONFIG = """ | ||
kubernetes_hpa: | ||
description: Benchmarks how quickly hpa reacts to load | ||
vm_groups: | ||
default: | ||
vm_spec: *default_dual_core | ||
vm_count: 1 | ||
container_specs: | ||
kubernetes_fib: | ||
image: fibonacci | ||
container_registry: {} | ||
container_cluster: | ||
cloud: GCP | ||
type: Kubernetes | ||
vm_count: 1 | ||
vm_spec: *default_dual_core | ||
nodepools: | ||
fibpool: | ||
vm_count: 3 | ||
vm_spec: | ||
GCP: | ||
machine_type: n2-standard-4 | ||
AWS: | ||
machine_type: m6i.xlarge | ||
Azure: | ||
machine_type: Standard_D4s_v5 | ||
""" | ||
|
||
|
||
def GetConfig(user_config: Dict[str, Any]) -> Dict[str, Any]: | ||
"""Load and return benchmark config. | ||
Args: | ||
user_config: user supplied configuration (flags and config file) | ||
Returns: | ||
loaded benchmark configuration | ||
""" | ||
config = configs.LoadConfig(BENCHMARK_CONFIG, user_config, BENCHMARK_NAME) | ||
|
||
return config | ||
|
||
|
||
def _PrepareCluster(benchmark_spec: bm_spec.BenchmarkSpec): | ||
"""Prepares a cluster to run the hpa benchmark.""" | ||
cluster: container_service.KubernetesCluster = ( | ||
benchmark_spec.container_cluster | ||
) | ||
fib_image = benchmark_spec.container_specs['kubernetes_fib'].image | ||
|
||
cluster.ApplyManifest( | ||
'container/kubernetes_hpa/fib.yaml.j2', | ||
fib_image=fib_image, | ||
runtime_class_name=FLAGS.kubernetes_hpa_runtime_class_name, | ||
) | ||
|
||
cluster.WaitForResource('deploy/fib', 'available', namespace='fib') | ||
|
||
|
||
def _PrepareLocust(benchmark_spec: bm_spec.BenchmarkSpec): | ||
"""Prepares a vm to run locust.""" | ||
vm = benchmark_spec.vms[0] | ||
locust.Install(vm) | ||
locust.Prep(vm, locust.Locustfile.RAMPUP) | ||
|
||
|
||
def Prepare(benchmark_spec: bm_spec.BenchmarkSpec): | ||
"""Install fib workload (and associated hpa) on the K8s Cluster. | ||
Args: | ||
benchmark_spec: The benchmark specification. Contains all data that is | ||
required to run the benchmark. | ||
""" | ||
|
||
prepare_fns = [ | ||
functools.partial(_PrepareCluster, benchmark_spec), | ||
functools.partial(_PrepareLocust, benchmark_spec), | ||
] | ||
|
||
background_tasks.RunThreaded(lambda f: f(), prepare_fns) | ||
|
||
|
||
def Run(benchmark_spec: bm_spec.BenchmarkSpec) -> List[Sample]: | ||
"""Run a benchmark against the Nginx server.""" | ||
|
||
# Get the SUT address | ||
stdout, _, _ = container_service.RunKubectlCommand([ | ||
'get', | ||
'-n', | ||
'fib', | ||
'svc/fib', | ||
'-o', | ||
"jsonpath='{.status.loadBalancer.ingress[0].ip}'", | ||
]) | ||
addr = 'http://' + stdout.strip() + ':5000' | ||
|
||
# Run locust against the SUT | ||
vm = benchmark_spec.vms[0] | ||
samples = locust.Run(vm, addr) | ||
|
||
return list(samples) | ||
|
||
|
||
def Cleanup(benchmark_spec): | ||
"""Cleanup.""" | ||
del benchmark_spec |
Oops, something went wrong.