Professional Documents
Culture Documents
Trynus Bakalavr
Trynus Bakalavr
До захисту допущено:
Завідувач кафедри
Вадим МУХІН
«___»___________2023 р.
Дипломна робота
на здобуття ступеня бакалавра
за освітньо-професійною програмою «Інтелектуальні сервіс-
орієнтовані розподілені обчислення»
спеціальності Комп’ютерні науки
на тему: «Механізми автоматичного масштабування та планування у
хмарних обчисленнях»
Виконав:
студент IV курсу, групи ДА-91
Тринус Нікіта Вячеславович__________
Керівник:
Доц. к.т.н
Булах Богдан Вікторович__________
Консультант з нормоконтролю:
доцент, к.т.н. Кирюша Б.А. __________
Рецензент: __________
ЗАТВЕРДЖУЮ
Завідувач кафедри
Вадим МУХІН
«___»____________2023р.
ЗАВДАННЯ
на дипломну роботу студенту Тринусу Нікіті Вячеславовичу
Календарний план
Термін
№ Назва етапів виконання виконання
Примітка
з/п дипломної роботи етапів
роботи
1 Отримання завдання 20.03.2023
АНОТАЦІЯ
до дипломної роботи Тринуса Нікіти
на тему:
«Механізми автоматичного масштабування та планування у хмарних
обчисленнях»
Структура дипломної роботи: Загальний об’єм пояснювальної записки:
95 сторінок, 48 рисунків, 6 таблиць, 32 посилання, 9 додаток.
ABSTRACTION
to the Trynus Nikita bachelor’s degree thesis
on the topic:
“Methods of autoscaling and scheduling in Cloud Computing”
Thesis structure: Total volume of the explanatory note: 95 pages, 48 figures,
6 tables, 32 references, 9 appendix.
Relevance of the topic. The most popular benefit of using autoscaling is the
potentially large costs savings. However, this technology also makes it possible to
increase the overall availability of the system, reduce the use of resources, decrease
the time required for system maintenance, make the system able to expand faster
and respond to unexpected load spikes.
ЗМІСТ
Зміст....................................................................................................................6
Вступ...................................................................................................................9
1. Хмарні Обчислення.................................................................................11
2. Kubernets...................................................................................................19
Висновки..........................................................................................................78
Додаток.............................................................................................................82
8
ВСТУП
1. ХМАРНІ ОБЧИСЛЕННЯ
2. KUBERNETS
4. АЛГОРИТМИ АВТОМАТИЧНОГО
МАСШТАБУВАННЯ
1. AR – Autoregressive component
- SARIMA:
І на це є кілька причин:
Рис. 5.1
1. Часові ряди
- ARIMA/SARIMA(Seasonal ARIMA)
- ARIMAX/SARIMAX(Seasonal ARIMAX)
2. Ансамблі
- XGBoost
3. Нейронні мережі
- LSTM
- GRU(більш швидкий, але менш якісний LSTM)
Рис. 5.20
Рис. 5.21
Якщо дані все ж таки мають певну циклічність як зображено на Рис. 5.23,
тоді відкривається певний простір для здійснення передбачень.
6. ФУНКЦІОНАЛЬНО-ВАРТІСНИЙ АНАЛІЗ
ПРОГРАМНОГО ПРОДУКТУ
Функціонально-вартісний аналіз – це підхід для оцінювання фактичної
вартості продукту. Всі кроки розбиваються на функції та підраховується
кожну частину продукту.
Функція F1:
1. Python
2. Golang
Функція F2:
1. GCP
2. AWS
3. Microsoft Azure
Функція F3:
1. MSE
2. MAPE
Функція F1:
Функція F3:
X1 оп/мс
120
100
80
60
40
20
0
1 2 3
X2 Мб
66
64
62
60
58
56
54
52
50
1 2 3
X3 мс
120
100
80
60
40
20
0
1 2 3
X4 с
95
90
85
80
75
70
65
60
55
50
1 2 3
X1 1 2 3 4 1 2 3 16 -1,5 2,25
69
X2 4 1 2 3 4 1 2 17 -0,5 0,25
X3 3 4 1 2 3 4 1 18 0,5 0,25
X4 2 3 4 1 2 3 4 19 1,5 2,25
Разом 10 10 10 10 10 10 10 70 0 5
Для погодження достовірності експертних оцінок потрібно знайти
параметри:
N – число експертів.
1
T = Ri =17.5
n
Δ i=Ri−T .
д) коефіцієнт конкордації:
12 S 12 ∙5
W= = =0,878>W k =0 , 67.
N ( n −n )
2 3
7 ( 33−3 )
2
bi
KB= i n
,
∑ ¿ bi
i=1
експертів;
a ij –коефіцієнт переваги i-го на j-тим параметром.
'
bi
KB= i n
,
∑ ¿b '
i
i=1
де b 'i=∑ ¿ aij b j.
j=1
Разом 16 1 59 1
де n – кількість параметрів;
K B – коефіцієнт вагомості i–го параметра;
i
Т О=Т Р ∙ К П ∙ К СК ∙ К М ∙ К СТ ∙ К СТ . М
К П – поправочний коефіцієнт;
програм;
К СТ . М – коефіцієнт стандартного математичного забезпечення.
дорівнює:
M
С Ч= грн .,
T m∙ t
С ЗП =С Ч ∙T i ∙ К Д ,
приладу у користувача;
К А – річна норма амортизації;
С ЕК 13705451.80
С МГ = = =50 , 70 грн /год .
Т ЕФ 1 67567 , 6
КК 8,4 −5
К ТЕР = = =8 , 72 ∙10
С ПП 96225.98
– Python
– GCP
– MSE
ВИСНОВКИ
У ході виконання дослідження було глибоко розглянуто тему автоматичного
масштабування та необхідності в даному інструменті. На мою думку, для
кожної задачі існує певна стратегія автоматичного масштабування, яка
78
ДОДАТОК
Metrics Extractor Service
import datetime
import time
from functools import reduce
import pandas as pd
import pika
import typer
import kubernetes_helper
app = typer.Typer()
DEPLOYMENT_NAME = "intelliscale-busy-server"
@app.command()
def run_metrics_collection(step_timeout: int):
metrics_api = kubernetes_helper.create_kubernetes_metrics_api_client()
connection =
pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))
channel = connection.channel()
channel.queue_declare(queue="metrics")
if metrics_api is None:
print("API was not initialized. Exiting...")
exit(1)
while True:
raw_metrics = kubernetes_helper.get_pods_metrics(
deployment_name=DEPLOYMENT_NAME, api=metrics_api
)
pods_count = len(raw_metrics)
pods_metrics = [
{"cpu": pod["usage"]["cpu"], "memory": pod["usage"]["memory"]}
for pod in raw_metrics
]
cpu = reduce(
lambda x, _: x + _,
map(
lambda pod:
kubernetes_helper.convert_cpu_metric(pod["cpu"]),
pods_metrics,
),
)
memory = reduce(
lambda x, _: x + _,
map(
lambda pod:
kubernetes_helper.convert_memory_metric(pod["memory"]),
pods_metrics,
),
)
cpu_utilization = cpu / (pods_count *
kubernetes_helper.LIMITS["cpu"])
83
memory_utilization = memory / (pods_count *
kubernetes_helper.LIMITS["memory"])
metrics = [
str(pods_count),
str(cpu),
str(memory),
str(cpu_utilization),
str(memory_utilization),
str(datetime.datetime.now()),
]
message = ",".join(metrics)
channel.basic_publish(exchange="", routing_key="metrics",
body=message)
print(" [x] Sent %r" % message)
time.sleep(step_timeout)
if __name__ == "__main__":
app()
Scaler Service
import datetime
import time
import pandas as pd
import pika
import typer
import kubernetes_helper
import ml_algorithms
import rule_based_algorithms
app = typer.Typer()
DEPLOYMENT_NAME: str = "intelliscale-busy-server"
@app.command()
def ping():
print("Pong")
# @app.command()
def main(
polling_timeout: float,
mode: str,
upper_cpu_threshold: float = 0.8,
bottom_cpu_threshold: float = 0.1,
scaling_period: int = 60,
backup_timeout: int = 30,
awaiting_timeout: int = 60,
metric_log_filename: str = "metrics_log.csv",
):
metrics_df = pd.DataFrame(
columns=[
84
"pods_count",
"cpu",
"memory",
"cpu_utilization",
"memory_utilization",
"timestamp",
]
) # type: ignore
apps_api = kubernetes_helper.create_kubernetes_apps_api_client()
if apps_api is None:
print("API was not initialized. Exiting...")
exit(1)
connection =
pika.BlockingConnection(pika.ConnectionParameters(host="localhost"))
channel = connection.channel()
channel.queue_declare(queue="metrics")
current_pods_count = 0
desired_pods_count = 0
last_backup_timestamp = datetime.datetime.now()
last_modification_timestamp = None
while True:
current_timestamp = datetime.datetime.now()
print("Polling...")
_, _, raw_metrics = channel.basic_get(queue="metrics",
auto_ack=True)
if raw_metrics:
metrics = raw_metrics.decode().split(",")
print(f"Receiving metric {metrics=}")
(
pods_count,
cpu,
memory,
cpu_utilization,
memory_utilization,
timestamp,
) = metrics
current_pods_count = int(pods_count)
if desired_pods_count != current_pods_count:
if last_modification_timestamp is not None:
if (
current_timestamp - last_modification_timestamp
).seconds > awaiting_timeout:
kubernetes_helper.update_deployment(
api=apps_api,
deployment_name=DEPLOYMENT_NAME,
replicas_count=desired_pods_count,
)
last_modification_timestamp =
datetime.datetime.now()
if (current_timestamp - last_backup_timestamp).seconds >
backup_timeout:
last_backup_timestamp = current_timestamp
metrics_df.to_csv(metric_log_filename, sep=",", index=False,
mode="w")
# metrics_df.drop(metrics_df.index, inplace=True)
time.sleep(polling_timeout)
if __name__ == "__main__":
main(1.0, "rule-based", 0.8, 60)
import utils
sns.set()
86
def generate_xgboost_model():
df = pd.read_csv("metrics_log_fixed.csv")
# df["cpu"] = df["cpu"].rolling(100).mean()
df = df.dropna()
df["timestamp"] = pd.to_datetime(df["timestamp"], format="%Y-%m-%d %H:
%M:%S.%f") # type: ignore
train_df = df[df["timestamp"] < str("2023-06-10 22:00:29.140694")]
test_df = df[df["timestamp"] > str("2023-06-10 22:00:29.140694")]
plt.plot(train_df["timestamp"], train_df["cpu"], label="Training", )
plt.plot(test_df["timestamp"], test_df["cpu"], label="Testing", )
train_df["hour"] = train_df["timestamp"].dt.hour
train_df["minute"] = train_df["timestamp"].dt.minute
train_df["second"] = train_df["timestamp"].dt.second
train_df["microsecond"] = train_df["timestamp"].dt.microsecond
test_df["hour"] = test_df["timestamp"].dt.hour
test_df["minute"] = test_df["timestamp"].dt.minute
test_df["second"] = test_df["timestamp"].dt.second
test_df["microsecond"] = test_df["timestamp"].dt.microsecond
prediction = trained_model.predict(x_test)
evaluate_model(y_test, prediction)
with open("xgboost_model.pkl", "wb") as file_obj:
pickle.dump(trained_model, file_obj) # pickle.load(), "rb"
def generate_lstm_model():
df = pd.read_csv("metrics_log.csv")
df = df[["cpu", "timestamp"]]
df["timestamp"] = pd.to_datetime(df["timestamp"], format="%Y-%m-%d %H:
%M:%S.%f")
df.index = df["timestamp"] # type: ignore
cpu = df["cpu"]
WINDOW_SIZE = 5
X1, y1 = df_to_X_y(cpu, WINDOW_SIZE)
dataset_length = len(X1)
train_split = int(0.7*dataset_length)
validation_split = train_split + int(0.15*dataset_length)
X_train1, y_train1 = X1[:train_split], y1[:train_split] # TODO: issue
van be here
X_val1, y_val1 = X1[train_split:validation_split],
y1[train_split:validation_split]
X_test1, y_test1 = X1[validation_split:], y1[validation_split:]
model1 = Sequential()
model1.add(InputLayer((5, 1)))
model1.add(LSTM(64))
model1.add(Dense(8, "relu"))
model1.add(Dense(1, "linear"))
model1.compile(loss=MeanSquaredError(),
optimizer=Adam(learning_rate=0.0001), metrics=[RootMeanSquaredError()])
model1.fit(X_train1, y_train1, validation_data=(X_val1, y_val1),
epochs=10)
cpu_predictions = model1.predict(X1).flatten()
test_results = pd.DataFrame(data={"Test Predictions":cpu_predictions,
"Actuals":y1})
evaluate_model(y_test=test_results["Actuals"],
prediction=test_results["Test Predictions"])
model1.compile(loss=MeanSquaredError(),
optimizer=Adam(learning_rate=0.0001), metrics=[RootMeanSquaredError()])
model1.fit(X1, y1, epochs=10)
model1.save("lstm_model")
generate_lstm_model()
88
ML Predictor
import datetime
import os
import pickle
import numpy as np
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
import logging
import utils
tf.get_logger().setLevel(logging.ERROR)
return pods_to_add + 1
import pandas as pd
def calculate_pods_count(
pods_count: int,
metrics_df: pd.DataFrame,
upper_threshold: float,
bottom_threshold: float,
scaling_period: int,
) -> int:
"""
Receiving pandas DataFrame
pd.DataFrame(
columns=[
"pods_count",
"cpu",
"memory",
"cpu_utilization",
"memory_utilization",
"timestamp",
]
)
"""
last_timestamp = datetime.datetime.strptime(
metrics_df["timestamp"].max(), "%Y-%m-%d %H:%M:%S.%f"
) - datetime.timedelta(seconds=scaling_period)
Testing Service
import concurrent.futures
import os
import time
app = typer.Typer()
@app.command()
def run_stress_load_campaign(requests_count: int):
print("Running stress load campaign")
start = time.time()
destination_url = f"{BASE_URL}/matrix/{100}"
for _ in range(requests_count):
response = requests.get(destination_url)
print(response.text)
print(f"Total execution time: {time.time() - start}")
@app.command()
def run_parallel_stress_load_campaign(requests_count: int):
print("Running stress load campaign")
start = time.time()
destination_url = f"{BASE_URL}/matrix/{100}"
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
futures = []
for _ in range(requests_count):
futures.append(executor.submit(requests.get,
url=destination_url))
for future in concurrent.futures.as_completed(futures):
future.result().text
print(f"Total execution time: {time.time() - start}")
@app.command()
def run_cyclic_stress_load_campaign(resolution: int, cycles: int):
print("Running cyclic load campaign")
length = np.pi * 2 * cycles
sin_wave = np.sin(np.arange(0, length, length / resolution))
start = time.time()
requests_strategy = abs((sin_wave*30).astype(int))
print(requests_strategy)
destination_url = f"{BASE_URL}/matrix/{100}"
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
futures = []
for requests_count in requests_strategy:
for _ in range(requests_count):
futures.append(executor.submit(requests.get,
url=destination_url))
for future in concurrent.futures.as_completed(futures):
print(future.result().text)
time.sleep(2)
91
print(f"Total execution time: {time.time() - start}")
@app.command()
def run_custom_stress_load_campaign():
print("Running cyclic load campaign")
start = time.time()
requests_strategy = [(50, 1), (300, 8), (500, 16), (600, 24)]
print(requests_strategy)
destination_url = f"{BASE_URL}/matrix/{100}"
for requests_count, threads_count in requests_strategy:
print(f"!!!!!! Running with params {requests_count=},
{threads_count=}")
with
concurrent.futures.ThreadPoolExecutor(max_workers=threads_count) as
executor:
futures = []
for _ in range(requests_count):
futures.append(executor.submit(requests.get,
url=destination_url))
for future in concurrent.futures.as_completed(futures):
future.result().text
time.sleep(10)
print(f"Total execution time: {time.time() - start}")
@app.command()
def run_custom_pods_load_campaign():
print("Running cyclic load campaign")
start = time.time()
requests_strategy = [(2000, 4), (2000, 8), (2000, 16), (2000, 24),
(2000, 32), (2000, 40), (2000, 48)]
print(requests_strategy)
destination_url = f"{BASE_URL}/matrix/{100}"
for requests_count, threads_count in requests_strategy:
print(f"!!!!!! Running with params {requests_count=},
{threads_count=}")
with
concurrent.futures.ThreadPoolExecutor(max_workers=threads_count) as
executor:
futures = []
for _ in range(requests_count):
futures.append(executor.submit(requests.get,
url=destination_url))
for future in concurrent.futures.as_completed(futures):
future.result().text
time.sleep(20)
print(f"Total execution time: {time.time() - start}")
if __name__ == "__main__":
app()
Kubernetes Helper
import typing
MEMORY_TRANSFORM_TABLE = {
"Ei": 1_152_921_504_606_846_976,
"Pi": 1_125_899_906_842_624,
"Ti": 1_099_511_627_776,
"Gi": 1_073_741_824,
"Mi": 1_048_576,
"Ki": 1_024,
}
LIMITS = {
"cpu": 1.0,
"memory": 1_073_741_824.0,
}
return api
return api
def get_pods_metrics(
deployment_name: str, api: client.CustomObjectsApi
) -> typing.List[typing.Dict[str, typing.Any]]:
print(f"Extracting metrics for deployment {deployment_name}")
k8s_pods = api.list_cluster_custom_object("metrics.k8s.io", "v1beta1",
"pods")
pods_metrics = []
return cpu
return memory_bytes
Busy Server
main.py
from fastapi import FastAPI
import utils
import time
import logging
app = FastAPI()
logger = logging.getLogger("uvicorn")
logger.setLevel(logging.DEBUG)
@app.get("/ping")
def ping():
return {"response": "pong"}
@app.get("/matrix/{size}")
async def matrix_multiplication(size: int):
matrix_a = utils.create_non_singular_matrix(size=size)
start = time.time()
logger.info("Creating matrix took %.5f", start - time.time())
matrix_b = matrix_a
94
start = time.time()
matrix_c = utils.multiply_matrix(matrix_a=matrix_a, matrix_b=matrix_b)
logger.info("Multiplying matrix took %.5f", start - time.time())
determinant = utils.calculate_determinant(matrix=matrix_c)
start = time.time()
logger.info("Calculating determinant of matrix took %.5f", start -
time.time())
logger.info("Determinant is %.5f", determinant)
utils.py
import typing
import random
def multiply_matrix(
matrix_a: typing.List[typing.List[int]], matrix_b:
typing.List[typing.List[int]]
) -> typing.List[typing.List[int]]:
c = [[] for _ in range(len(matrix_a))]
for i in range(len(matrix_a)):
for j in range(len(matrix_a)):
sum = 0
for k in range(len(matrix_a)):
sum += matrix_a[i][k] * matrix_b[k][j]
c[i].append(sum)
return c
for i in range(size):
for j in range(size):
matrix[i][j] = (i + 1) * (j + 1) + random.randint(-5, 5)
return matrix
if n != len(matrix[0]):
raise ValueError("The matrix must be square.")
det = 0
det = 1
for i in range(n):
det *= temp[i][i]
return det