Skalowanie aplikacji
Usługi chmurowe, laboratorium 6
Agenda
- Stan startowy i limity planu F1
- Scale up: F1 -> B1
- Pomiar bazowy: load test jednej instancji
- Scale out: dwie instancje, session affinity
- Ten sam test na dwóch instancjach, metryki
- Eksperyment: stan lokalny na wielu instancjach
- Bottleneck: wspólna baza danych
- Autoscale: idea i wymagania planu
- Raport, powrót do F1, sprzątanie
Cel
Sprawdzić w praktyce:
externalized state -> możliwość uruchomienia wielu instancji
- scale up i scale out na tej samej aplikacji
- pomiar przepustowości przed i po
- rozkład ruchu i metryki per instancja
- co się dzieje, gdy stan jest lokalny
- gdzie kończy się skalowanie aplikacji
Kod CloudNotes się nie zmienia.
Architektura: przed i w trakcie
przed (cp05):
Managed Database (psql-cloudnotes-<SUFFIX>)
^
Client -> HTTPS -> App Service (plan F1, 1 instancja)
v
Blob Storage (stcloudnotes<SUFFIX>)
w trakcie:
+-> instancja 1 --+
Client -> HTTPS -> load balancer --+ +-> Managed Database
App Service +-> instancja 2 --+-> Blob Storage
(plan B1, 2 instancje)
Po zajęciach: z powrotem F1, 1 instancja, checkpoint cp05-database.
Scale up czy scale out?
| Scale up | Scale out | |
|---|---|---|
| zmiana | większa maszyna | więcej maszyn |
| App Service | poziom planu | liczba instancji |
| wymaganie | restart instancji | stateless, load balancer |
| limit | sufit jednej maszyny | limit planu, wspólne zależności |
| awaria maszyny | usługa stoi | pozostałe instancje działają |
Limity planów App Service
| Poziom | Instancje | Skalowanie | Linux, Sweden Central |
|---|---|---|---|
| F1 Free | 1 | brak | 0 USD |
| B1 Basic | do 3 | ręczne | 0,018 USD/h za instancję |
| S1 Standard | do 10 | ręczne + reguły autoscale | 0,095 USD/h za instancję |
| P0v3 Premium v3 | do 30 | + automatic scaling | 0,089 USD/h za instancję |
F1: limit czasu CPU. Nie uruchamiaj load testów na F1.
Koszt
- B1, 1-2 instancje przez zajęcia: około 0,1 USD z kredytu
- PostgreSQL i Storage: jak na L05
- Płacisz za każdą instancję planu, nie za aplikację
Ryzyko: zapomniany B1 z 2 instancjami = około 26 USD miesięcznie
Cleanup: 2 -> 1 instancja, potem B1 -> F1.
Nie usuwaj limitu wydatków, nie przechodź na Pay-As-You-Go.
Krok 1: stan startowy
az resource list --resource-group rg-cloudnotes --query "[].{name:name, type:type}" --output table
az postgres flexible-server start --resource-group rg-cloudnotes --name psql-cloudnotes-<SUFFIX>
git -C ~/cloudnotes archive --format=zip --output ~/cloudnotes-app.zip HEAD:src
az webapp deploy --resource-group rg-cloudnotes --name app-cloudnotes-<SUFFIX> \
--src-path ~/cloudnotes-app.zip --type zip
sudo apt-get install -y apache2-utils
Oczekiwane backendy: postgres, blob, sync.
Brak serwera bazy: „Odtworzenie stanu cp05-database” w L05.
Krok 2: jedna instancja, limit F1
az appservice plan show --resource-group rg-cloudnotes --name plan-cloudnotes-<SUFFIX> \
--query "{sku:sku.name, tier:sku.tier, instances:sku.capacity}" --output table
az webapp list-instances --resource-group rg-cloudnotes --name app-cloudnotes-<SUFFIX> --query "[].name" --output tsv
az appservice plan update --resource-group rg-cloudnotes --name plan-cloudnotes-<SUFFIX> --number-of-workers 2
- F1: błąd albo nadal
1instancja - Identyfikator instancji: stopka strony i pole
instancew/health
Krok 3: scale up F1 -> B1
az appservice plan update --resource-group rg-cloudnotes --name plan-cloudnotes-<SUFFIX> --sku B1
curl -s https://app-cloudnotes-<SUFFIX>.azurewebsites.net/health | jq "{status, instance, started_at}"
B1,Basic, 1 instancja, maksymalnie 3- nowy
instanceistarted_at - notatki zostały
Scale up = nowa maszyna. Też wymaga stateless.
Krok 4: pomiar bazowy
/debug/cpu?ms=250: każde żądanie zajmuje CPU na 250 ms
ab -n 80 -c 8 -l "https://app-cloudnotes-<SUFFIX>.azurewebsites.net/debug/cpu?ms=250" | tee ~/l06-ab-1.txt
grep -E "Failed requests|Requests per second|Time per request" ~/l06-ab-1.txt
| Wartość | Oczekiwanie | Skąd |
|---|---|---|
Requests per second | około 4 | 1 rdzeń / 250 ms |
Time per request | około 2000 ms | 8 w kolejce x 250 ms |
Bottleneck: CPU jednej instancji. Przeciążenie = rosnący czas, nie błędy.
Krok 5: scale out do 2 instancji
az appservice plan update --resource-group rg-cloudnotes --name plan-cloudnotes-<SUFFIX> --number-of-workers 2
seq 20 | xargs -I{} curl -s https://app-cloudnotes-<SUFFIX>.azurewebsites.net/api/notes \
| jq -r '"\(.instance) notatek: \(.notes | length)"' | sort | uniq -c
11 3f7a9c2e81b4 notatek: 3
9 a04d5e6f7c12 notatek: 3
Dwie instancje, te same dane. Load balancer wbudowany w App Service.
Session affinity
curl -s -o /dev/null -D - https://app-cloudnotes-<SUFFIX>.azurewebsites.net/ | grep -i "set-cookie"
az webapp update --resource-group rg-cloudnotes --name app-cloudnotes-<SUFFIX> --client-affinity-enabled false
- Ciasteczko
ARRAffinityprzykleja przeglądarkę do jednej instancji curlnie trzyma ciasteczek, więc trafia w obie- Affinity to obejście dla stanu w pamięci, nie rozwiązanie
- CloudNotes jest stateless -> affinity off (zostaje po zajęciach)
Krok 6: ten sam test, dwie instancje
ab -n 80 -c 8 -l "https://app-cloudnotes-<SUFFIX>.azurewebsites.net/debug/cpu?ms=250" | tee ~/l06-ab-2.txt
grep -E "Requests per second|Time per request" ~/l06-ab-1.txt ~/l06-ab-2.txt
| 1 instancja | 2 instancje | |
|---|---|---|
Requests per second | około 4 | około 8 |
Time per request | około 2000 ms | około 1000 ms |
Prawie liniowo, bo bottleneck był w każdej instancji osobno.
Metryki per instancja
az monitor metrics list --resource plan-cloudnotes-<SUFFIX> --resource-group rg-cloudnotes \
--resource-type Microsoft.Web/serverfarms --metric CpuPercentage --aggregation Maximum --dimension Instance \
--query 'value[0].timeseries[].{instance:metadatavalues[0].value, cpu:data[?maximum].maximum}' --output json
Portal: plan -> Metrics -> CPU Percentage -> Apply splitting -> Instance
- test 1: jedna seria blisko 100%
- test 2: obie serie wysoko
- metryki spływają z opóźnieniem kilku minut
Krok 7: stan lokalny na wielu instancjach
az webapp config appsettings delete --resource-group rg-cloudnotes --name app-cloudnotes-<SUFFIX> \
--setting-names DATABASE_URL --output none
seq 6 | xargs -I{} curl -s -o /dev/null -w "%{http_code} " -X POST \
-F "title=Lokalna {}" -F "content=Zapisana na instancji" https://app-cloudnotes-<SUFFIX>.azurewebsites.net/notes
12 3f7a9c2e81b4: Lokalna 5, Lokalna 3, Lokalna 2, Lokalna 1
8 a04d5e6f7c12: Lokalna 6, Lokalna 4
Każda instancja ma własne notatki i własną notatkę #1.
Krok 7: przywrócenie i wniosek
az webapp config appsettings set --resource-group rg-cloudnotes --name app-cloudnotes-<SUFFIX> \
--settings DATABASE_URL="postgresql://cloudnotesadmin:<HASLO>@psql-cloudnotes-<SUFFIX>.postgres.database.azure.com:5432/cloudnotes?sslmode=require" \
--output none
local state -> problem ze skalowaniem -> externalized state -> wiele instancji
- Affinity ukryłoby problem do restartu, wdrożenia albo scale in
- Notatki
Lokalnaprzepadły: jedna zmiana konfiguracji = utrata danych
Krok 8: bottleneck przenosi się na bazę
ab -n 200 -c 8 -l "https://app-cloudnotes-<SUFFIX>.azurewebsites.net/api/notes" | tee ~/l06-ab-db.txt
az monitor metrics list --resource psql-cloudnotes-<SUFFIX> --resource-group rg-cloudnotes \
--resource-type Microsoft.DBforPostgreSQL/flexibleServers --metric connections_succeeded --aggregation Total \
--query 'value[0].timeseries[0].data[?total].{time:timeStamp, connections:total}' --output table
- 1 żądanie = 1 nowe połączenie z bazą (brak puli połączeń)
- B1ms:
max_connections50, dla aplikacji 35 - 10 instancji x 4 procesy = 40 ->
too many clients
Co jest wspólne dla wszystkich instancji i ile wytrzyma?
Jak odciążyć bazę?
| Sposób | Kompromis |
|---|---|
| connection pool w aplikacji | zmiana kodu, rozmiar puli x liczba instancji |
| pooler przed bazą (PgBouncer) | niedostępny wbudowany w Burstable |
| cache | dane mogą być nieaktualne |
| scale up bazy | koszt, przerwa, sufit jednej maszyny |
| repliki do odczytu | opóźnienie replikacji, koszt |
Aplikacje skalujemy zwykle poziomo, bazy relacyjne pionowo.
Krok 9: autoscale
metryka -> reguła (próg, okno) -> akcja (+1 / -1) -> cooldown -> ponowna ocena
| Element | Przykład dla CloudNotes |
|---|---|
| scale out | średnie CPU > 70% przez dziesięć minut -> +1 |
| scale in | średnie CPU < 30% przez dziesięć minut -> -1 |
| minimum / maksimum | 2 / 4 |
- Reguły: od S1. Automatic scaling: Premium v2/v3
- Minimum = odporność, maksimum = budżet i baza
- Portal: plan -> Scale out (App Service plan). Nie zmieniaj poziomu
Weryfikacja
Raport ~/l06-report.txt (polecenie w instrukcji), przed cleanupem:
- plan
B1, 2 instancje, affinityfalse - rozkład żądań: dwie instancje, ta sama liczba notatek
- 3 load testy,
Failed requests: 0, 2 instancje szybsze niż 1 - eksperyment: różne listy notatek
Lokalna - CPU per instancja, połączenia do bazy
/health:healthy,"database":"postgres"
Sprawdź: w raporcie nie ma postgresql://, AccountKey= ani hasła.
Sprzątanie
Kolejność ma znaczenie: F1 pozwala tylko na 1 instancję.
az appservice plan update --resource-group rg-cloudnotes --name plan-cloudnotes-<SUFFIX> --number-of-workers 1
az appservice plan update --resource-group rg-cloudnotes --name plan-cloudnotes-<SUFFIX> --sku F1
az appservice plan show --resource-group rg-cloudnotes --name plan-cloudnotes-<SUFFIX> \
--query "{sku:sku.name, instances:sku.capacity}" --output table
rm ~/cloudnotes-app.zip
| Wariant z L05 | Serwer bazy | Na L07 |
|---|---|---|
| A: bezpłatna oferta | az postgres flexible-server stop ... | start |
| B: brak oferty | az postgres flexible-server delete ... --yes | odtworzenie cp05 |
| B1 App Service | az group delete --name rg-cloudnotes ... | odtworzenie cp04 i cp05 |
Podsumowanie
- Scale up = większa maszyna, ma sufit; scale out = więcej maszyn
- Funkcje skalowania zależą od poziomu planu: F1 nic, B1 ręcznie, S1 reguły
- PaaS: load balancer wbudowany, nowa instancja bez zmiany kodu
- 2 instancje przy obciążeniu CPU: około 2 razy większa przepustowość
- Stan lokalny + wiele instancji = różne dane dla użytkowników
- Session affinity ukrywa problem, nie rozwiązuje go
- Skalowanie aplikacji przenosi bottleneck na wspólną bazę
- Autoscale: metryka, próg, okno, krok, minimum, maksimum
- Płacisz za każdą instancję: po teście wróć do F1
Następnie: L07, awaria, restart i podstawy reliability.