• Akademia
  • Blog
  • O Serverless
  • O stronie

Jak stworzyć usługę sieciową (webserwis) za pomocą AWS Lambda?


Jak stworzyć usługę sieciową (webserwis) za pomocą AWS Lambda?
W poprzedniej lekcji stworzyliśmy pierwszą funkcje AWS Lambda. Pokazałem Ci również podstawowe komendy oraz zasadę działania Serverless Framework. w tej lekcji rozwiniemy temat dalej i wywołamy naszą funkcję przez protokół HTTP.

Ten artykuł jest częścią większego cyklu: Kurs Serverless

Na cały cykl składają się następujące artykuły. Jeśli jesteś tutaj pierwszy raz, to dobrze będzie zacząć od początku 😄
  1. Jak zainstalować Serverless Framework?
  2. Jak skonfigurować AWS CLI?
  3. Pierwsza funkcja Lambda
  4. Jak stworzyć usługę sieciową (webserwis) za pomocą AWS Lambda?
  5. "Śledź Paczkę" - wideokurs serverless

Rozpocznijmy od stworzenia nowego projektu w Serverless Framework:

1
2
cd ~/slspl
serverless create --template aws-nodejs -p lekcja04

Zastąp treść wygenerowanego przez automat pliku serverless.yml następującym kodem:

[serverless.yml]
1
2
3
4
5
6
7
8
9
10
11
12
service: lekcja04 
provider:
name: aws
runtime: nodejs8.10
region: eu-central-1
functions:
hello:
handler: handler.hello
events:
- http:
path: /lekcja04
method: get

W stosunku do konfiguracji z poprzedniej lekcji dodałem tutaj:

  • informacje o regionie gdzie zostanie umieszczona funkcja (linijka 5)
  • definicję zdarzenia, które wywoła naszą funkcję (linijki 9 - 12)

Co to są regiony?
Regiony AWS to obszary geograficzne, w których Amazon Web Services posiada swoje serwerownie (datacenters). Na każdy region składają się minimum trzy strefy dostępności (ang. availability zone - AZ). Każda z nich jest zupełnie niezależna od pozostały (prąd, internet itp.) i fizycznie znajduje się w innym miejscu. Strefy dostępności są potrzebne aby zapwnić dużą dostępnośc naszych rozwiązań.

Przykładowo, gdy w klęsce żywiołowej jedno datacenter zostanie uszkodzone i przestanie działać, to wciąż możemy korzystać z pozostałych dwóch. Od architektów chmury wymaga się, aby uwzględnili ten fundamentalny koncept projektując swoje rozwiązania. W przypadku niektórych usług, jak chociażby maszyny wirtualne EC2 musimy sami zdefiniować, w której strefie dostępności będą one uruchomione. Jeśli zależy nam na wysokiej dostępności naszego rozwiązania to musimy mieć minimum dwie maszyny wirtualne EC2 w dwóch różnych strefach dostępności na wypadek awarii jednej ze stref.

W przypadku Lambdy definiujemy tylko region, a AWS zdecyduje gdzie fizycznie się ona uruchomi. My nie musimy się martwić o wysoką dostępność rozwiązania (po przez projektowanie odpowiedniej infrastruktury korzystającej z dwóch lub więcej stref dostępności), ponieważ jest ona wbudowana w usługę AWS Lambda.

Region eu-central-1 to obecnie najbliższy nam geograficznie region znajdujący się we Frankfurcie w Niemczech.

Zdarzenia

Wracając do pliku serverless.yml.

1
2
3
4
events:
- http:
path: /lekcja04
method: get

Podsekcja events w definicji funkcji definiuje jakie zdarzenia (ang. event) będą mogły wywołać (ang. trigger) funkcję. Powyższa konfiguracja umożliwi wywołanie funkcji po przez żądanie typu GET wysłane na https://<losowe_id>.execute-api.eu-central-1.amazonaws.com/dev/lekcja04.

Świetnie, co nie?
4 linijki kodu i mamy webserwis. Magia!

Nie do końca. Powyższa konfiguracją mówi frameworkowi aby przygotował dla nas API endpoint w usłudze AWS API Gateway. Endpoint będzie dostępny pod adresem URL podanym powyżej.
W webkonsoli AWS w szczegółach naszej funkcji zobaczymy następującą konfigurację:
Szczegóły funkcji

Po lewej stronie widać listę różnych typów zdarzeń, które są w stanie wywołać funkcję Lambda. Pochodzą one z różnych usług AWS i można je rozumieć jako prostą i standardową metodę integracji różnych usług z AWS Lambda.

Po środku widać API Gateway, który został skonfigurowany jako zdarzenie wywołujące naszą funkcję.

Kod funkcji lambda

Plik handler.js zastąp następującym kodem

[handler.js]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';

module.exports.hello = async (event, context) => {
const { path, httpMethod, headers } = event
const userAgent = headers['User-Agent']
return {
statusCode: 200,
body: JSON.stringify({
message: 'Serverless Polska jest super',
path,
httpMethod,
userAgent
})
}
}

Powyższy kod zwróci nam kilka wybranych wartości z obiektu event przekazanego do naszej funkcji przez AWS. Linijka 4 to taki cukier syntaktyczny w JavaScript, nazywa się to destrukturyzacją obiektów.

Teraz wystarczy tylko zdeployować kod do chmury. Wykonaj komendę sls deploy. Co powinno dać następujący efekt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (361 B)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............................
Serverless: Stack update finished...
Service Information
service: lekcja04
stage: dev
region: eu-central-1
stack: lekcja04-dev
api keys:
None
endpoints:
GET - https://iyfccefmse.execute-api.eu-central-1.amazonaws.com/dev/lekcja04
functions:
hello: lekcja04-dev-hello
layers:
None

Serverless Framework zbudował dla nas funkcję wraz z endpointem API Gateway, i log grupą w CloudWatch.
Najbardziej interesującym elementem jest oczywiście URL.

Skopiuj go i wywołaj prze curl lub npm Postmanem jeśli nie lubisz curla 😅

1
2
$ curl https://iyfccefmse.execute-api.eu-central-1.amazonaws.com/dev/lekcja04
{"message":"Serverless Polska jest super","path":"/lekcja04","httpMethod":"GET","userAgent":"curl/7.58.0"}

Powyższa komenda zwraca wyniki w postaci JSONa. Aby wygodniej się czytało przepuśćmy go przez jq:

1
2
3
4
5
6
7
$ curl https://iyfccefmse.execute-api.eu-central-1.amazonaws.com/dev/lekcja04 -s | jq
{
"message": "Serverless Polska jest super",
"path": "/lekcja04",
"httpMethod": "GET",
"userAgent": "curl/7.58.0"
}

Jak widać nic odkrywczego tutaj nie znajdziemy, wartości są przewidywalne. Natomiast zachęcam Cię do modyfikacji kody i zwrócenia całego obiektu event - w ten sposób zobaczysz jaki informacje są dostępne dla funkcji.

Jak wywołać funkcje z parametrem?

W tym momencie zastanawiasz się pewnie jak przekazać jakieś parametry do funkcji. Jest to bardzo proste i nie wymaga, żadnych zmian w konfiguracji. Musimy jedynie zmodyfikować nieco kod naszej funkcji.

Zastąp kod handler.js poniższym przykładem

[handler.js]
1
2
3
4
5
6
7
8
9
10
11
12
'use strict';

module.exports.hello = async (event, context) => {
const { queryStringParameters } = event
const name = queryStringParameters && queryStringParameters.name ? queryStringParameters.name : 'Przybyszu'
return {
statusCode: 200,
body: JSON.stringify({
message: `Witaj ${name} na kursie serverless!`
})
};
};

Zgodnie ze specyfikacją, wszelkie parametry przekazane funkcji w URL zostaną dla nas sparsowane przez AWS i dostarczone wygodnie w obiekcie event.queryStringParameters. W linijce 5 definiuje, że jeśli została przekazana jakaś wartość w parametrze name to zostanie ona użyta, jeśli nie to wypiszemy Przybyszu w naszym powitaniu.

Teraz uwaga, nowa komenda!

Zawsze gdy piszemy sls deploy Serverless Framework próbuje zaktualizować konfiguracje, w tym zasoby naszego projektu, innymi słowy generuje i uruchamia szablon CloudFormation. To chwilę trwa. Jeśli nie dokonaliśmy żadnych zmian w pliku serverless.yml to nie ma sensu marnować na to czasu. Można wywołać sls deploy -f <nazwa_funkcji>, aby zaktualizować tylko sam kod źródłowy funkcji.

1
2
3
4
5
6
$ sls deploy -f hello
Serverless: Packaging function: hello...
Serverless: Excluding development dependencies...
Serverless: Uploading function: hello (351 B)...
Serverless: Successfully deployed function: hello
Serverless: Successfully updated function: hello

Trwa do duuuuużo krócej niż pełen deploy.

Teraz możemy przetestować wywołanie funkcji:

1
2
3
4
5
6
7
8
9
10
$ curl -s https://iyfccefmse.execute-api.eu-central-1.amazonaws.com/dev/lekcja04 | jq
{
"message": "Witaj Przybyszu na kursie serverless!"
}


$ curl -s https://iyfccefmse.execute-api.eu-central-1.amazonaws.com/dev/lekcja04\?name\=Pawel | jq
{
"message": "Witaj Pawel na kursie serverless!"
}

Parametr name przekazujemy w URLu ponieważ nasza funkcja jest ustawiona, aby reagować na metodę GET protokołu HTTP. Oczywiście w praktyce się tak nie programuje. Zmieńmy zatem konfigurację aby działała z metodą POST i przyjmowała parametry w postaci JSONa.

Po pierwsze należy zmienić konfigurację w pliku serverless.yml. W linijce 12 zmień get na post. Następnie podmień kod funkcji na:

[handler.js]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
'use strict';

module.exports.hello = async (event, context) => {
console.log(`Recieved parameters: ${event.body}`);
let name = 'Przybyszu'
if (event.body) {
const params = JSON.parse(event.body)
name = params.name ? params.name : name
}
return {
statusCode: 200,
body: JSON.stringify({
message: `Witaj ${name} na kursie serverless!`
})
};
};

W linijce 4 pojawiło się logowanie przychodzących parametrów. Jak widać w przeciwieństwie do poprzedniego przypadku z HTTP GET, parametry przekazane metoda POST są umieszczane w obiekcie event.body. Oczywiście jeśli nic nie przekażemy do funkcji to body będzie nullem, stąd sprawdzenie w linijce 6. Następnie parsujemy parametry, ponieważ zostały one przekazane jako stringified JSON. Jeśli name zostało przekazane to ustawiamy jego wartość na otrzymaną.

Ponieważ dokonaliśmy zmian w konfiguracji to musimy zrobić pełen deploy komendą: sls deploy.

Następnie możemy przejść do testowania:

1
2
3
4
5
6
7
8
9
$ curl -s https://iyfccefmse.execute-api.eu-central-1.amazonaws.com/dev/lekcja04 | jq
{
"message": "Witaj Przybyszu na kursie serverless!"
}

$ curl -s https://iyfccefmse.execute-api.eu-central-1.amazonaws.com/dev/lekcja04 -d '{"name": "Paweł", "param": "value"}' | jq
{
"message": "Witaj Paweł na kursie serverless!"
}

Za pomocą parametru -d możemy przekazać więcej niż jeden parametr.

A co dziej się z logami z linijki 4?
Logi trafiają do AWS CloudWatch Logs. Jak już wcześniej wspominałem Serverless Framework tworzy log grupę do naszej funkcji automatycznie. Logi możemy obejrzeć logując sie do AWS przez przeglądarkę ale wygodnie będzie to zrobić przez komendę sls logs -f <nazwa_funkcji>.

1
2
3
4
5
6
7
8
9
10
$ sls logs -f hello
START RequestId: 7f4ebc11-057f-418d-8e82-fccd60ce5cc5 Version: $LATEST
2019-01-26 11:01:44.269 (+01:00) 7f4ebc11-057f-418d-8e82-fccd60ce5cc5 Recieved parameters: null
END RequestId: 7f4ebc11-057f-418d-8e82-fccd60ce5cc5
REPORT RequestId: 7f4ebc11-057f-418d-8e82-fccd60ce5cc5 Duration: 1.69 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 21 MB

START RequestId: 7c4344ce-df15-4ce9-b290-1440e7c84f98 Version: $LATEST
2019-01-26 11:01:48.565 (+01:00) 7c4344ce-df15-4ce9-b290-1440e7c84f98 Recieved parameters: {"name": "Paweł", "param": "value"}
END RequestId: 7c4344ce-df15-4ce9-b290-1440e7c84f98
REPORT RequestId: 7c4344ce-df15-4ce9-b290-1440e7c84f98 Duration: 0.74 ms Billed Duration: 100 ms Memory Size: 1024 MB Max Memory Used: 21 MB

Widzimy tutaj logi z dwóch powyższych wywołań funkcji. Za pierwszym razem nie podaliśmy parametrów stad null. Przy drugim uruchomieniu widać już przekazane parametry.

W logach możemy też zobaczyć inne ciekawe rzeczy jak:

  • czas trwania - Duration: 0.74 ms
  • czas za który płacimy - Billed Duration: 100 ms
  • pamięć przydzielona funkcji - Memory Size: 1024 MB
  • pamięć wykorzystana prze funkcję - Max Memory Used: 21 MB

Jak widać 1GB pamięci dla tak prostej funkcji to overkill. W jednej z następnych lekcji omówimy dokładniej temat pamięci.

Na dzisiaj to już koniec. Życzę Ci udanego dnia i nie zapomnij usunąć swojej funkcji (sls remove)