dpzc-4/04_Public_cloud/04_zadania.ipynb
nlitkowski e248075771 a
2022-01-05 01:08:54 +01:00

364 lines
41 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Zadania domowe\n",
"\n",
"W ramach tego modułu do zdobycia są 4 punkty. \n",
"\n",
"> **Uwaga**: Zdanie 4.1 można wykonać conajwyżej 2 razy u różnych dostawców. Pozostałe zadania można wykonać tylko raz.\n",
"\n",
"> **Uwaga**: Zadanie 4.2 można wykonać tylko u dostawców oferujących usługę automatycznego skalowania (AWS, Azure).\n",
"\n",
"> **Uwaga**: Łącznie można wykonać zadania za znacznie więcej niż dostępnych 4 punktów. Uzyskany wynik zostanie jednak obcięty do wartości maksymalnej.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zadanie 4.1 (2 punkty)\n",
"Celem tego zadania jest przedstawienie rozwiązania tylu *load balancer*.\n",
"\n",
"Zadanie polega na uruchomieniu prostego webserwisu w infrastrukturze chmurowej w wielu instancjach jednocześnie z zapewnieniem pojedynczego punktu dostępowego, który będzie dynamicznie delegował żądania do dostępnych instancji.\n",
"\n",
"Webserwis udostępnia jeden endpoint HTTP `/factors/<liczba>`, zwracający odpowiedź w formacie `JSON`, która zawiera listę czynników pierwszych liczby zawartej w żądaniu. Przesłana w żądaniu liczba musi być liczbą całkowitą, większą od 1, w przeciwnym razie serwis zwraca kod błędu 400. Nie ma ograniczenia na wielkość danych wejściowych. Dla dostatecznie dużej liczby czas oczekiwania na odpowiedź może być bardzo długi.\n",
"\n",
"Przykładowo dla zapytania\n",
"```bash\n",
"curl <adres_serwera>:<port>/factors/10\n",
"```\n",
"odpowiedź to\n",
"```json\n",
"[2, 5]\n",
"```\n",
"\n",
"Dany jest program w postaci binarnej (skompilowanej na architekturę `amd64`, jednowątkowy), realizujący ten webserwis na porcie 8080.\n",
"\n",
"### Sposób sprawdzania zadania\n",
"Zadanie będzie sprawdzane półautomatycznie. Do skryptu sprawdzającego należy podać adres URL webserwisu. Sprawdzarka będzie w kilku wątkach jednocześnie wysyłała żądania pod wskazany adres. Jeden wątek (t0) wysyłać będzie małe liczby, dla których rozkład będzie zajmował poniżej sekundy. W tym samym czasie kolejne wątki, dodawane w regularnych odstępach czasu, będą wysyłały bardzo duże liczby, dla których rozkład na czynniki pierwsze będzie wymagał przynajmniej kilku sekund. Zadanie zostanie zaliczone wtedy, gdy wątek wysyłający małe liczby (t0) będzie otrzymywać odpowiedzi z serwera szybko, bez oczekiwania na rozłożenie większych liczb.\n",
"\n",
"Poniżej przykładowe wykresy dla różnych konfiguracji:\n",
"\n",
"* brak równoważenia obciążenia (1 worker) \n",
"\n",
" ![brak równoważenia obciążenia (1 worker)](obrazy/Z01_1worker.png)\n",
"* równoważenie obciążenia bez skalowania (2 workery) \n",
"\n",
" ![równoważenie obciążenia bez skalowania (2 workery)](obrazy/Z01_2workers.png)\n",
"* równoważenie obciążenia (4 workery) \n",
"\n",
" ![równoważenie obciążenia (4 workery) ](obrazy/Z01_4workers.png)\n",
"* równoważenie obciążenia bez skalowania (5 workerów) \n",
"\n",
" ![równoważenie obciążenia bez skalowania (5 workerów)](obrazy/Z01_5workers.png)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.096s\tfast\t\n",
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.096s\tfast\t\n",
"INFO:root:Thread: 1\treqs: 2\tmean time: 1.403s\t\n",
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.098s\tfast\t\n",
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.096s\tfast\t\n",
"INFO:root:Thread: 1\treqs: 2\tmean time: 1.539s\t\n",
"INFO:root:Thread: 2\treqs: 2\tmean time: 2.062s\t\n",
"INFO:root:Thread: 0\treqs: 4\tmean time: 0.194s\tfast\t\n",
"INFO:root:Thread: 1\treqs: 1\tmean time: 2.307s\t\n",
"INFO:root:Thread: 0\treqs: 4\tmean time: 0.149s\tfast\t\n",
"INFO:root:Thread: 2\treqs: 2\tmean time: 1.845s\t\n",
"INFO:root:Thread: 3\treqs: 1\tmean time: 2.299s\t\n",
"INFO:root:Thread: 0\treqs: 4\tmean time: 0.196s\tfast\t\n",
"INFO:root:Thread: 1\treqs: 2\tmean time: 2.098s\t\n",
"INFO:root:Thread: 3\treqs: 2\tmean time: 1.765s\t\n",
"INFO:root:Thread: 2\treqs: 1\tmean time: 2.092s\t\n",
"INFO:root:Thread: 0\treqs: 4\tmean time: 0.164s\tfast\t\n",
"INFO:root:Thread: 4\treqs: 1\tmean time: 2.503s\t\n",
"INFO:root:Thread: 1\treqs: 1\tmean time: 2.268s\t\n",
"INFO:root:Thread: 2\treqs: 1\tmean time: 2.200s\t\n",
"INFO:root:Thread: 3\treqs: 1\tmean time: 2.651s\t\n",
"INFO:root:Thread: 0\treqs: 4\tmean time: 0.183s\tfast\t\n",
"INFO:root:Thread: 1\treqs: 1\tmean time: 2.306s\t\n",
"INFO:root:Thread: 4\treqs: 1\tmean time: 2.493s\t\n",
"INFO:root:Thread: 2\treqs: 1\tmean time: 2.536s\t\n",
"INFO:root:Thread: 3\treqs: 1\tmean time: 2.698s\t\n",
"INFO:root:Thread: 0\treqs: 4\tmean time: 0.179s\tfast\t\n",
"INFO:root:Thread: 4\treqs: 2\tmean time: 1.972s\t\n",
"INFO:root:Thread: 2\treqs: 1\tmean time: 2.053s\t\n",
"INFO:root:Thread: 0\treqs: 4\tmean time: 0.140s\tfast\t\n",
"INFO:root:Thread: 3\treqs: 1\tmean time: 2.207s\t\n",
"INFO:root:Thread: 1\treqs: 1\tmean time: 2.321s\t\n",
"INFO:root:Thread: 3\treqs: 1\tmean time: 2.080s\t\n",
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.101s\tfast\t\n",
"INFO:root:Thread: 2\treqs: 2\tmean time: 1.699s\t\n",
"INFO:root:Thread: 1\treqs: 2\tmean time: 1.651s\t\n",
"INFO:root:Thread: 1\treqs: 1\tmean time: 1.333s\t\n",
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.104s\tfast\t\n",
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.099s\tfast\t\n",
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.098s\tfast\t\n",
"INFO:root:Thread: 0\treqs: 5\tmean time: 0.100s\tfast\t\n"
]
}
],
"source": [
"import requests\n",
"import random\n",
"import math\n",
"import time\n",
"import threading\n",
"import logging\n",
"logging.getLogger().setLevel(logging.INFO)\n",
"\n",
"\n",
"API_URL=\"http://localhost:8080\"\n",
"\n",
"\n",
"UNIT = 5.0 # secs\n",
"\n",
"# Pre generated primes\n",
"first_primes_list = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29,\n",
" 31, 37, 41, 43, 47, 53, 59, 61, 67,\n",
" 71, 73, 79, 83, 89, 97, 101, 103,\n",
" 107, 109, 113, 127, 131, 137, 139,\n",
" 149, 151, 157, 163, 167, 173, 179,\n",
" 181, 191, 193, 197, 199, 211, 223,\n",
" 227, 229, 233, 239, 241, 251, 257,\n",
" 263, 269, 271, 277, 281, 283, 293,\n",
" 307, 311, 313, 317, 331, 337, 347, 349]\n",
"\n",
"\n",
"def nBitRandom(n):\n",
" return random.randrange(2**(n-1)+1, 2**n - 1)\n",
" \n",
"def getLowLevelPrime(n):\n",
" '''Generate a prime candidate divisible\n",
" by first primes'''\n",
" while True:\n",
" # Obtain a random number\n",
" pc = nBitRandom(n)\n",
" \n",
" # Test divisibility by pre-generated\n",
" # primes\n",
" for divisor in first_primes_list:\n",
" if pc % divisor == 0 and divisor**2 <= pc:\n",
" break\n",
" else: return pc\n",
" \n",
"def isMillerRabinPassed(mrc):\n",
" '''Run 20 iterations of Rabin Miller Primality test'''\n",
" maxDivisionsByTwo = 0\n",
" ec = mrc-1\n",
" while ec % 2 == 0:\n",
" ec >>= 1\n",
" maxDivisionsByTwo += 1\n",
" assert(2**maxDivisionsByTwo * ec == mrc-1)\n",
" \n",
" def trialComposite(round_tester):\n",
" if pow(round_tester, ec, mrc) == 1:\n",
" return False\n",
" for i in range(maxDivisionsByTwo):\n",
" if pow(round_tester, 2**i * ec, mrc) == mrc-1:\n",
" return False\n",
" return True\n",
" \n",
" # Set number of trials here\n",
" numberOfRabinTrials = 20\n",
" for i in range(numberOfRabinTrials):\n",
" round_tester = random.randrange(2, mrc)\n",
" if trialComposite(round_tester):\n",
" return False\n",
" return True\n",
" \n",
"def random_large_prime(bits):\n",
" while True:\n",
" prime_candidate = getLowLevelPrime(bits)\n",
" if not isMillerRabinPassed(prime_candidate):\n",
" continue\n",
" else:\n",
" return prime_candidate\n",
"\n",
"def thread_function(i, fast, timeout):\n",
" start = time.time()\n",
"\n",
" c = 5 # bits: 20: 200ms; 21: 350ms; 22: 700ms 23: 1.5s; 25: 6s; 26: 10s; 27: 24s\n",
" bits = 19 if fast else 23\n",
" last_report = time.time()\n",
" processing_time = 0.0\n",
" reqs = 0\n",
" while True:\n",
" iter_start = time.time()\n",
" if iter_start - start > timeout:\n",
" logging.info(\"Thread: %d\\treqs: %d\\tmean time: %.3fs\\t%s\"%(i, reqs, processing_time/reqs if reqs>0 else 0.0, \"fast\\t\" if fast else \"\"))\n",
" results[i][iter_start] = processing_time/reqs if reqs>0 else 0.0\n",
" return\n",
" if iter_start - last_report > UNIT/2:\n",
" if len(results[i])%2 == 0:\n",
" logging.info(\"Thread: %d\\treqs: %d\\tmean time: %.3fs\\t%s\"%(i, reqs, processing_time/reqs if reqs>0 else 0.0, \"fast\\t\" if fast else \"\"))\n",
" results[i][iter_start] = processing_time/reqs if reqs>0 else 0.0\n",
" processing_time = 0.0\n",
" reqs = 0\n",
" last_report=iter_start\n",
"\n",
" factors = [random_large_prime(bits) for i in range(c)]\n",
" factors.sort()\n",
" n=math.prod(factors)\n",
"\n",
" r = requests.get(API_URL+'/factors/%d'%(n))\n",
" if r.status_code != 200:\n",
" logging.error(\"wrong status code from webservice\")\n",
" else:\n",
" result = r.json()\n",
" if result != factors:\n",
" logging.error(\"Wrong factors\")\n",
"\n",
" processing_time+=time.time() - iter_start\n",
" reqs+=1\n",
" time.sleep(0.5)\n",
"\n",
"START = time.time()\n",
"slow_threads = 4\n",
"\n",
"results = [ {} for i in range(slow_threads+1)]\n",
"\n",
"t0 = threading.Thread(target=thread_function, args=(0, True, (5 + slow_threads*3) * UNIT))\n",
"t0.start()\n",
"time.sleep(2 * UNIT)\n",
"for i in range(slow_threads):\n",
" t = threading.Thread(target=thread_function, args=(i+1, False, (slow_threads-i) * 3 * UNIT))\n",
" t.start()\n",
" time.sleep(2 * UNIT)\n",
"\n",
"t0.join()"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
}
],
"source": [
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import scipy.stats as stats\n",
"mu = 0\n",
"std = 1\n",
"for i, result in enumerate(results):\n",
" x = [(x - START)/UNIT for x in result.keys()]\n",
" y = result.values()\n",
" plt.plot(x, y, label=\"t%d\"%(i,))\n",
"\n",
"plt.legend()\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zadanie 4.2 (2 punkty + 2 punkty za 4.1)\n",
"\n",
"Zadanie polega na rozbudowaniu rozwiązania 4.1 o usługę automatycznego skalowania. Webserwis w wariancie początkowym powinien dysponować 2 instancjami (jak w zadaniu 4.1). Jednak w przypadku wzrostu ilości żądań powinien automatycznie przeskalować się poprzez dodanie nowych instancji (do maksymalnie 5).\n",
"\n",
"### Sposób sprawdzania zadania\n",
"Zadanie będzie sprawdzane półautomatycznie z wykorzystaniem skryptu z zadania poprzedniego.\n",
"\n",
"> **Wskazówka**: Mechaznim autoamtycznego skalowania potrzebuje trochę czasu aby zadziałać, konieczne może okazać się zwiększenie wartości zmiennej UNIT z 5 sekund do 30-60 sekund.\n",
"\n",
"## Zadanie 4.3 (2 punkty, wymaga zaliczenia zadania 4.1 lub 4.2)\n",
"Zautomatyzuj wykonanie zadania 4.1 lub 4.2 tak aby cała infrastruktura (łącznie z zasobami chmurowymi, konfiguracją maszyn i oprogramowania) wykonywana była przez odpowiedni skrypt za pośrednictwem jednej komendy w konsoli. Na koniec swojego działania komenda ma wyświetlać URL dostępowy do webserwisu.\n",
"\n",
"Sam skrypt oraz wszystkie niezbędne pliki (np. cloud-init, pliki konfiguracyjne, itp.) powinny znajdować się na repozytorium Git.\n",
"\n",
"> Uwaga: Pamiętaj aby przygotować stosowną dokumentację (w formacie Markdown w pliku README.md) opisującą konfigurację środowiska wymaganą przez ten skrypt (autoryzacja do platform chmury obliczeniowej, zmienne środowiskowe, itp.). Nie powinna ona jednak być zbyt skomplikowana i wykraczać znacznie poza autoryzację do chmury.\n",
"\n",
"> Wskazówka: Technologia w której przygotowany zostanie skrypt nie jest ograniczona, wymagane jest tylko umożliwienie uruchomienia jej na systemie Linux (np. Ubuntu/Debian) bez potrzeby nadmiernej rekonfiguracji systemu. W przypadku potrzeby skorzystania z niestandardowych narzędzi, zalecane jest skorzystanie z narzędzia Docker, zbudowanie specjalnego obrazu i wykonanie skryptu z jego poziomu.\n",
"\n",
"### Sposób sprawdzania zadania\n",
"Zadanie sprawdzane jest ręcznie poprzez sklonowanie repozytorium Git, wykonanie konfiguracji zgodnie z dokumentacją, wywołanie odpowiedniego skryptu i ręczne sprawdzenie czy system został zbudowany zgodnie ze specyfikacją. Skrypt będzie wykonywany w środowisku zapewnionym i skonfigurowanym przez prowadzącego zajęcia. Przykład sposobu wykonania skryptu przez prowadzącego:\n",
"\n",
"```bash\n",
"git clone git@git.wmi.amu.edu.pl:s<index>/<repo_name>.git\n",
"cd <repo_name>\n",
"./deploy.sh\n",
"```\n",
"\n",
"Po zakończeniu skryptu deploy, przeprowadzona zostanie procedura sprawdzania zadania 4.1 lub zadania 4.2."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Zadanie 4.4 (3 punkty)\n",
"Zadanie ma na celu uświadomienie różnic w cennikach różnych dostawców usług chmurowych.\n",
"\n",
"> **Wskazówka**: Zadanie można realizować we dwie osoby, obie otrzymują wtedy taką samą ilość punktów.\n",
"\n",
"Zadanie polega na przeanalizowaniu kosztów wdrożenia przykładowego systemu informatycznego u różnych dostawców chmury obliczeniowej. Rozwiązaniem zadania będzie raport przygotowany w formacie PDF porównujący koszt wdrożenia danego systemu w przynajmniej 4 wariantach u przynajmniej dwóch różnych dostawców. Oprócz łącznego kosztu (startowego i miesiecznego) należy porównać też koszt poszczególnych składowych, uwzględnić możliwe promocje (reserved, spot, itp). Zakładamy, że system będzie działał nieprzerwanie przez rok.\n",
"\n",
"**Specyfikacja systemu**\n",
"\n",
"> **Uwaga**: Opisywana specyfikacja dotyczy rzeczywistego systemu informatycznego będącego kopalnią kryptowalut.\n",
"\n",
"1. Główny węzłeł sieci kryptowalutowej. Wymagane przynajmniej 32GB RAM, 8vCPU, 600GB dysku twardego. Wymagania specjalne: bardzo duża wydajność dysku twardego, transfer sieciowy na poziomie 2-3TB/miesiąc.\n",
"1. Zapasowy węzeł sieci kryptowalutowej. Wymagane przynajmniej 8GB RAM, 3vCPU, 600GB dysku twardego. Wymagania specjalne: duża wydajność dysku twardego, transfer sieciowy na poziomie 2-3TB/miesiąc.\n",
"1. Baza danych Redis. Wymagane przynajmniej 2GB RAM, 2vCPU, 10GB dysku twardego. Wymagania specjalne: wysoka dostępność, brak dostępu do bazy danych uniemożliwia działania wszyskich innych modułów\n",
"1. Moduł kopania. Wymagane przynajmniej 4GB RAM, 8vCPU, 10GB dysku twardego. Wymagania specjalne: transfer sieciowy na poziomie 1TB/miesiąc, stały adres IP\n",
"1. Moduł API i WWW. Wymagane przynajmniej 4GB RAM, 2vCPU, 10GB dysku twardego.\n",
"1. Moduł wypłat. Wymagane przynajmniej 2GB RAM, 1vCPU, 10GB dysku twardego.\n",
"\n",
"Każdy moduł, baza danych oraz węzły powinny działać w odseparowanym środowisku z zachowaniem następująch połączeń:\n",
"* wszystkie moduły łączą sie z bazą danych\n",
"* moduł kopania łączy się z węzłem głównym i zapasowym\n",
"* moduł wypłat łączy się z węzłem zapasowym"
]
}
],
"metadata": {
"interpreter": {
"hash": "98b0a9b7b4eaaa670588a142fd0a9b87eaafe866f1db4228be72b4211d12040f"
},
"kernelspec": {
"display_name": "Python 3.9.5 64-bit ('base': conda)",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.6"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}