AWS na lokalnej maszynie? To możliwe z localstack

Czym jest localstack?

Localstack jest aplikacją (albo też zbiorem kilku), która pozwala na symulowanie serwisów AWS na lokalnej maszynie. Oczywiście nie ma wsparcia dla wszystkich serwisów, ale z tymi najpopularniejszymi nie będzie problemu, pełna lista aktualnie wspieranych usług jest poniżej (na dzień 1.06.2019):

  • API Gateway: http://localhost:4567
  • Kinesis: http://localhost:4568
  • DynamoDB: http://localhost:4569
  • DynamoDB Streams: http://localhost:4570
  • Elasticsearch: http://localhost:4571
  • S3: http://localhost:4572
  • Firehose: http://localhost:4573
  • Lambda: http://localhost:4574
  • SNS: http://localhost:4575
  • SQS: http://localhost:4576
  • Redshift: http://localhost:4577
  • ES (Elasticsearch Service): http://localhost:4578
  • SES: http://localhost:4579
  • Route53: http://localhost:4580
  • CloudFormation: http://localhost:4581
  • CloudWatch: http://localhost:4582
  • SSM: http://localhost:4583
  • SecretsManager: http://localhost:4584
  • StepFunctions: http://localhost:4585
  • CloudWatch Logs: http://localhost:4586
  • STS: http://localhost:4592
  • IAM: http://localhost:4593

Powyższa lista zawiera nazwy serwisów oraz ich domyślny adres. Po uruchomieniu localstacka na lokalnej maszynie, każdy serwis AWS ma przypisany osobny port który pozwala na dostęp do niego. No dobrze, ale jak to uruchomić i czemu ma mi się to właściwie przydać?

Po co mi localstack?

W zależności od tego w jaki sposób wykorzystujemy usługi AWS, localstack może się okazać kompletnie nieprzydatny lub wręcz przeciwnie – może znacznie przyspieszyć nasz development. W najprostszym scenariuszu możemy dzięki niemu zapoznać się z API wspieranych serwisów, czy to przez aws-cli czy aws-sdk dla różnych języków (np. Java, Golang, NodeJS). Innym zastosowaniem jest odpalanie testów w kompletnej izolacji, to znaczy modyfikując dane w DynamoDB nie musimy martwić się że przez przypadek usuniemy/zmodyfikujemy więcej niż byśmy chcieliśmy, możemy też oprzeć na nim nasze testy integracyjne, bez generowania kosztów na AWS.

Z reguły jeśli chcemy uruchomić testy integracyjne lub akceptacyjne to musimy najpierw naszą aplikację wypuścić na któreś ze środowisk (dev/stage/prod) lub też odpalić na lokalnej maszynie z potrzebnymi uprawnieniami i konfiguracją oraz mieć nadzieję, że nie zepsujemy wspólnego środowiska dla innych. Problem nie występuje, jeśli każdy developer ma swoje prywatne środowisko developerskie w chmurze, ale w dalszym ciągu musimy czekać aż nasz pipeline wypuści nową wersję (o ile oczywiście korzystasz z automatyzacji wdrażania zmian).

Localstack pozwala nam ominąć te problemy, ale kosztem jest utrzymywanie jego konfiguracji, nie wspiera uprawnień z IAM, oraz nie przetestujemy czy nasze nowe IAM policy pozwoli nam wysłać wiadomość na SQS. Daje nam jednak możliwość przetestowania czy dobrze wywołaliśmy funkcję z SDK, a także umożliwi uruchamianie testów integracyjnych w kompletnej izolacji na lokalnej maszynie, bez marnowania czasu na deploymenty, z opcją hot reloadu kodu (np. w Node.JS poprzez odpalenie naszego serwisu za pomocą nodemon) lub debuggera.

Jak zacząć?

Najłatwiejszym sposobem na uruchomienie localstacka jest skorzystanie z oficjalnego obrazu dockera. W poniższym przykładzie spróbujemy uruchomić localstack ze wsparciem dla usługi S3, stworzymy bucket i wyślemy do niego plik.

Uruchomiliśmy nowy kontener dockera z udostępnionymi trzema portami 4572, 4569, 8080. Dodatkowo ustawiliśmy zmienną o nazwie SERVICES z wartościami s3 i dynamodb, które oznaczają mniej więcej tyle że uruchomiony kontener powinien umożliwić dostęp do serwisu s3 i dynamodb. Nie użyliśmy flagi -d, także aktualny terminal jest zablokowany, ale za to możemy przejrzeć logi które są na bieżąco generowane przez localstack (co w początkowych fazach zabawy może być przydatne).

Aktualnie mamy działający kontener z S3 i DynamoDB. Dynamo nam się nie przyda (ale dobrze wiedzieć jak wystartować więcej niż 1 serwis), za to wykorzystamy S3 do stworzenia bucketa i załadowania tam pliku. W nowym oknie terminala wpisz:

Właściwie powyższy snippet wygląda dokładnie tak samo jakbyśmy działali na prawdziwym serwisie AWS. Jedyną różnicą jest argument --endpoint http://localhost:4572, który powoduje że zapytania które normalnie powinny być wysłane do amazona, lądują u nas na lokalnej maszynie na porcie 4572, na tym porcie działa wcześniej uruchomiona usługa S3 z dockerowego kontenera. Ogólne wnioski są takie że z localstack możemy korzystać tak jak ze standardowych serwisów AWS, z tą różnicą że musimy zawsze nadpisać argument --endpoint odpowiednim hostem i portem. Jeśli chcemy sprawdzić aktualny stan localstacka, to możemy w przeglądarce wejść na adres http://localhost:8080 powinniśmy tam zobaczyć nasz bucket o nazwie test.

Jeśli nie chcemy za każdym razem nadpisywać endpointu serwisu, z którego chcemy skorzystać, to możemy zainstalować małego wrappera na aws-cli który zrobi to za nas awscli-local.

A jak użyć tego w kodzie?

Na podobnej zasadzie możemy używać localstacka w naszym kodzie, aws-sdk pozwala na nadpisanie zmiennej endpoint.

Przykład dla S3 w javascript (pamiętaj o wywołaniu npm i aws-sdk, przed uruchomieniem poniższego kodu):

Powyższy snippet robi mniej więcej to samo co bash, który wywołaliśmy wcześniej, Jedyną ciekawostką jest parameter s3ForcePathStyle: true. Okazuje się, że w przypadku S3 localstack nie działa 1 do 1 jak AWS i potrzebuje tego argumentu żeby poprawnie zapisać pliki.

Localstack z docker-compose oraz testy

W tym przykładzie pójdziemy o krok dalej. Zautomatyzujemy uruchamianie localstacka oraz konfigurację jego serwisów, ale zacznijmy od kodu, stwórzmy plik index.js:

Powyższy kod ma za zadanie uruchomić aplikację słuchającą na porcie 3000. Ma ona tylko jeden endpoint z metodą POST, a zadaniem tego endpointu jest zapisanie otrzymanego payloadu z requesta do dynamodb. Potrzebujemy jeszcze uproszczonego Dockerfile by uruchomić naszą aplikację w kontenerze:

Kolejnym krokiem będzie dodanie docker-compose.yml:

Tu już dzieję się troszkę więcej, uruchamiamy 3 kontenery items-app, localstack, setup-localstack.

  • items-app to nasza aplikacja którą przed chwilą stworzyliśmy
  • localstack to jak nazwa wskazuje kontener localstacka, skonfigurowany by udostępnić endpoint z web-ui oraz dynamodb
  • setup-localstack jest kontenerem który będzie miał za zadanie skonfigurować naszego localstacka, podpinamy pod niego skrypt init-localstack.sh który będzie miał za zadanie stworzenie dynamodb

Poniżej jest zawartość skryptu init-localstack.sh:

Powyższy skrypt konfiguruje aws-cli oraz tworzy tabelę w DynamoDB o nazwie items. Tabela ta ma wymagany atrybut id, który też jest jej indeksem.

Zainicjalizujmy jeszcze nasz projekt (dla npm init wystarczą nam wartości domyślne):

A więc mamy już wszystkie składniki, także pora odpalić naszą aplikację i zobaczyć jak ona działa (pamiętajmy o wyłączeniu kontenera, który wystartowaliśmy wcześniej):

Po paru sekundach wszystko powinno wystartować bez problemów. Możemy to zweryfikować otwierając przeglądarkę i wchodząc na adres http://localhost:8080 – zobaczymy web UI localstacka i naszą tabele w DynamoDB.

Kolejnym krokiem będzie zapisanie prostego obiektu w naszej bazie:

Powinniśmy zobaczyć wiadomość że operacja się udała. Ale jak sprawdzić czy coś rzeczywiście jest w naszej bazie? Możemy użyć do tego aws-cli:

Jako ćwiczenie zachęcam do dodania endpointu z metodą GET.

Nasza aplikacja działa w kompletnej izolacji. Możemy teraz napisać do niej przykładowy test tests-integration/index.spec.js:

Zapiszmy powyższy plik w folderze test-integration, teraz możemy odpalić powyższy kod np. za pomocą mocha:

Kompletny kod powyższego przykładu możemy znaleźc w repozytorium localstack-sample-micro.

Bardziej rozbudowany przykład znajduje się w repozytorium localstack-sample Jest tam aplikacja z kilkoma endpointami zapisującymi do DynamoDB, a także kolejka SQS, z której wiadomości są także umieszczane w bazie. Testy i konfiguracja aplikacji też jest zdecydowanie bardziej rozbudowana.

Podsumowanie

To by było na tyle. Początkowe kroki z localstackiem mogą być irytujące i można by uznać że nakład pracy włożony w konfigurację się nie zwróci, ale z chwilą kiedy zapoznamy się lepiej z tym narzędziem może ono nam naprawdę ułatwić życie i przyspieszyć naszą pracę.

Programista z domieszką DevOps, lubię wszystko co związane z technologiami ułatwiającymi nam pracę. Głównie zajmuję się pisaniem microserviców oraz ich procesem deploymentu do clouda. A pozatym to motocykl, planszówki, seriale i dobra książka.
PODZIEL SIĘ