Akceptujemy 10 000 wydarzeń w Yandex.Cloud. Część 1

Cześć wszystkim, przyjaciele!

* Ten artykuł został napisany na podstawie otwartego praktycznego warsztatu REBRAIN & Yandex.Cloud; jeśli wolisz oglądać wideo, możesz znaleźć je pod tym linkiem — https://youtu.be/cZLezUm0ekE

Niedawno mieliśmy okazję osobiście przetestować Yandex.Cloud. Ponieważ chcieliśmy zrealizować coś znacznie bardziej zaawansowanego, od razu odrzuciliśmy pomysł na uruchomienie prostego bloga na WordPressie z bazą danych w chmurze — to zbyt nudne. Po krótkich rozmyślaniach zdecydowaliśmy się na opracowanie czegoś, co przypominałoby architekturę produkcyjną serwisu do przyjmowania i analizy wydarzeń w czasie bliskim rzeczywistemu.

Jestem absolutnie pewny, że większość biznesów online (i nie tylko) w taki czy inny sposób gromadzi ogrom informacji o swoich użytkownikach i ich działaniach. Co najmniej jest to konieczne do podejmowania pewnych decyzji — na przykład, jeśli zarządzasz grą online, możesz przeanalizować statystyki, na którym poziomie użytkownicy najczęściej utknęli i usunęli Twoją grę. Lub dlaczego użytkownicy opuszczają Twoją stronę, nie dokonując zakupu (cześć, Yandex.Metrica).

Tak więc, nasza historia: jak napisaliśmy aplikację w golang, testowaliśmy kafka vs rabbitmq vs yqs, pisaliśmy strumieniowanie danych do klastra Clickhouse i wizualizowaliśmy dane za pomocą Yandex Datalens. Oczywiście wszystko to było doprawione infrastrukturą w postaci dockera, terraforma, gitlab ci i, oczywiście, prometheusa. Zaczynamy!

Od razu chcę zaznaczyć, że nie uda nam się skonfigurować wszystkiego w jednej sesji — potrzebujemy kilku artykułów w serii. Trochę o strukturze:

Część 1 (czytasz ją teraz). Określimy wymagania oraz architekturę rozwiązania, a także napiszemy aplikację w golang.
Część 2. Wprowadzamy naszą aplikację na produkcję, czynimy ją skalowalną i przeprowadzamy testy obciążeniowe.
Część 3. Spróbujemy zrozumieć, dlaczego musimy przechowywać wiadomości w buforze, a nie w plikach, oraz porównamy między sobą kafka, rabbitmq i Yandex Queue Service.
Część 4. Będziemy rozwijać klaster Clickhouse, pisać strumieniowanie danych do przenoszenia danych z bufora oraz konfigurujemy wizualizację w Datalens.
Część 5. Doprowadzimy całą infrastrukturę do porządku — skonfigurujemy ci/cd, używając gitlab ci, podłączymy monitoring i service discovery za pomocą prometheusa i consul.

Wymagania

Najpierw sformułujemy wymagania techniczne — co dokładnie chcemy osiągnąć na wyjściu.

  1. Chcemy mieć endpoint w postaci events.kis.im (kis.im — testowa domena, którą będziemy używać w całym artykule), który powinien odbierać zdarzenia za pomocą HTTPS.
  2. Zdarzenia to prosty JSON w postaci: {"event": "view", "os": "linux", "browser": "chrome"}. Na etapie końcowym dodamy nieco więcej pól, ale nie wpłynie to znacznie na całość. Jeśli chcesz, możesz przejść na protobuf.
  3. Usługa powinna być w stanie przetwarzać 10 000 zdarzeń na sekundę.
  4. Powinna istnieć możliwość poziomej skalowalności — poprzez proste dodawanie nowych instancji do naszego rozwiązania. Dobrze by było, gdybyśmy mogli przenieść część frontendową do różnych lokalizacji geograficznych, aby zmniejszyć opóźnienia w żądaniach od klientów.
  5. Odporność na awarie. Rozwiązanie powinno być wystarczająco stabilne i umieć przetrwać awarię dowolnych części (do określonej liczby, oczywiście).

Architektura

Do tego typu zadań od dawna wymyślono klasyczne architektury, które pozwalają na efektywne skalowanie. Na rysunku przedstawiono przykład naszego rozwiązania.

Akceptujemy 10 000 wydarzeń w Yandex.Cloud. Część 1

Więc, co mamy:

1. Po lewej stronie przedstawione są nasze urządzenia, które generują różne zdarzenia, czy to przechodzenie poziomów przez graczy w grze na smartfonie, czy tworzenie zamówienia w sklepie internetowym przez zwykłą przeglądarkę. Zdarzenie, jak określono w specyfikacji, to prosty JSON, który jest wysyłany na nasz endpoint — events.kis.im.

2. Pierwsze dwa serwery to proste load balancery, których główne zadania to:

  • Zachować ciągłą dostępność. Można do tego wykorzystać na przykład keepalived, który przełączy wirtualny IP między węzłami w przypadku problemów.
  • Zakończyć TLS. Tak, zakończymy TLS właśnie na nich. Po pierwsze, aby nasze rozwiązanie odpowiadało specyfikacji, a po drugie, aby zdjąć obciążenie związane z ustanowieniem szyfrowanego połączenia z naszych serwerów backendowych.
  • Równoważyć przychodzące żądania na dostępne serwery backendowe. Kluczowe słowo tutaj to — dostępne. W związku z tym dochodzimy do zrozumienia, że load balancery powinny być w stanie monitorować nasze serwery z aplikacjami i przestać równoważyć ruch na awarie węzły.

3. Za load balancerami znajdują się serwery aplikacyjne, na których uruchomiona jest wystarczająco prosta aplikacja. Powinna ona być w stanie przyjmować przychodzące żądania przez HTTP, walidować przesłany JSON i przechowywać dane w buforze.

4. Na diagramie jako bufor pokazano Kafka, chociaż oczywiście na tym poziomie można używać także innych podobnych usług. Porównamy Kafka, RabbitMQ i YQS w trzecim artykule.

5. Przedostatnim punktem naszej architektury jest Clickhouse — kolumnowa baza danych, która umożliwia przechowywanie i przetwarzanie ogromnych ilości danych. Na tym etapie musimy przenieść dane z bufora do samego systemu magazynowania (o tym w czwartym artykule).

Taki schemat pozwala nam niezależnie skalować poziomo każdą warstwę. Jeśli serwery backendowe nie dają rady — dodajemy kolejne — ponieważ są to aplikacje stateless, co oznacza, że można to zrobić nawet w trybie automatycznym. Jeśli bufor w postaci Kafka nie wytrzymuje - dodajemy więcej serwerów i przenosimy na nie część partycji naszego tematu. Jeśli Clickhouse nie daje rady — to już nie jest możliwe 🙂 W rzeczywistości również dodamy serwery i podzielimy dane na shard’y.

Przy okazji, jeśli zechcesz zrealizować opcjonalną część naszego specyfikacji i zrobić skalowanie w różnych geolokalizacjach, to nie ma nic prostszego:

Akceptujemy 10 000 wydarzeń w Yandex.Cloud. Część 1

W każdej geolokalizacji uruchamiamy load balancer z aplikacjami i Kafka. Ogólnie, wystarczą 2 serwery aplikacji, 3 węzły Kafka oraz chmurowy balancer, na przykład Cloudflare, który będzie sprawdzał dostępność węzłów aplikacyjnych i balansował zapytania według geolokalizacji na podstawie pierwotnego adresu IP klienta. Dzięki temu dane wysłane przez amerykańskiego klienta trafią na amerykańskie serwery, a dane z Afryki — na serwery afrykańskie.

Dalej wszystko jest naprawdę proste — używamy narzędzia mirror z zestawu Kafka i kopiujemy wszystkie dane ze wszystkich lokalizacji do naszego centralnego data center znajdującego się w Rosji. Wewnątrz rozdzielamy dane i zapisujemy je w Clickhouse do dalszej wizualizacji.

Tak więc, z architekturą już się zapoznaliśmy — zaczynamy testować Yandex.Cloud!

Tworzymy aplikację

Aby przejść do chmury, musimy jeszcze trochę poczekać i napisać dość prostą usługę do obsługi przychodzących zdarzeń. Użyjemy golang, ponieważ bardzo dobrze sprawdził się jako język do pisania aplikacji sieciowych.

Poświęcając godzinę (może dwie), uzyskujemy mniej więcej coś takiego: https://github.com/RebrainMe/yandex-cloud-events/blob/master/app/main.go.

Jakie główne punkty chciałbym tu zaznaczyć:

1. Podczas uruchamiania aplikacji można podać dwa flagi. Jeden z nich odpowiada za port, na którym będziemy nasłuchiwać przychodzące zapytania http (-addr). Drugi dotyczy adresu serwera kafka, do którego będziemy zapisywać nasze zdarzenia (-kafka):

addr     = flag.String("addr", ":8080", "Adres TCP do nasłuchiwania")
kafka    = flag.String("kafka", "127.0.0.1:9092", "Punkty końcowe Kafka")

2. Aplikacja korzysta z biblioteki sarama ([] github.com/Shopify/sarama) do wysyłania wiadomości do klastra kafka. Od razu ustawił w niej konfigurację, skoncentrowaną na maksymalnej szybkości przetwarzania:

config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForLocal
config.Producer.Compression = sarama.CompressionSnappy
config.Producer.Return.Successes = true

3. Nasza aplikacja ma również wbudowanego klienta prometheus, który zbiera różne metryki, takie jak:

  • liczba zapytań do naszej aplikacji;
  • liczba błędów podczas wykonywania zapytania (niemożność odczytu zapytania post, uszkodzony json, niemożność zapisu do kafka);
  • czas przetwarzania jednego zapytania od klienta, w tym czas zapisu wiadomości do kafka.

4. Trzy punkty końcowe, które obsługuje nasza aplikacja:

  • /status — просто возвращаем ok, чтобы показать, что мы живы. Хотя можно и добавить некоторые проверки, типа доступности кафка кластера.
  • /metrics — по этому url prometheus client будет возвращать собранные им метрики.
  • /post — основной endpoint, куда будут приходить POST запросы с json внутри. Наше приложение проверяет json на валидность и если все ок — записывает данные в кафка-кластер.

Dodam, że kod nie jest idealny — można go (i należy!) dopracować. Na przykład można zrezygnować z używania wbudowanego net/http i przejść na szybszy fasthttp. Można również zaoszczędzić czas przetwarzania i zasoby CPU, przenosząc walidację json na późniejszy etap — gdy dane będą przenoszone z bufora do klastra clickhouse.

Oprócz strony deweloperskiej, od razu pomyśleliśmy o naszej przyszłej infrastrukturze i podjęliśmy decyzję o wdrożeniu naszej aplikacji przez docker. Ostateczny Dockerfile do budowy aplikacji — https://github.com/RebrainMe/yandex-cloud-events/blob/master/app/Dockerfile. W sumie jest dość prosty, jedyny aspekt, na który chce zwrócić uwagę, to multistage build, który pozwala zmniejszyć końcowy obraz naszego kontenera.

Pierwsze kroki w chmurze

Na początku rejestrujemy się na cloud.yandex.ru. Po wypełnieniu wszystkich niezbędnych pól, zostanie utworzone dla nas konto i przyznany grant na określoną kwotę pieniędzy, którą można wykorzystać do przetestowania usług chmury. Jeśli chcesz powtórzyć wszystkie kroki z naszego artykułu, ten grant powinien wystarczyć.

Po rejestracji zostanie dla Ciebie utworzone oddzielne chmurki oraz katalog default, w którym można zacząć tworzyć zasoby chmurowe. Ogólnie rzecz biorąc, w Yandex.Cloud związki między zasobami wyglądają następująco:

Akceptujemy 10 000 wydarzeń w Yandex.Cloud. Część 1

Na jedno konto możesz stworzyć kilka chmur. A wewnątrz chmury utworzyć różne katalogi dla różnych projektów firmy. Więcej na ten temat możesz przeczytać w dokumentacji — https://cloud.yandex.ru/docs/resource-manager/concepts/resources-hierarchy. A tak przy okazji, poniżej w tekście będę na nią często się powoływać. Kiedy konfigurowałem całą infrastrukturę od podstaw — dokumentacja nie raz mnie ratowała, więc polecam się z nią zapoznać.

Do zarządzania chmurą można używać zarówno interfejsu webowego, jak i narzędzia wiersza poleceń — yc. Instalacja odbywa się jednym poleceniem (dla Linuxa i Mac Os):

curl https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash

Jeśli na temat uruchamiania skryptów z internetu obudził się w Tobie wewnętrzny procedurysta — po pierwsze, możesz otworzyć skrypt i go przeczytać, a po drugie, uruchamiamy go pod swoim użytkownikiem — bez praw roota.

Jeśli chcesz zainstalować klienta dla Windows, możesz skorzystać z instrukcji tutaj a następnie wykonać yc init, aby go całkowicie skonfigurować:

vozerov@mba:~ $ yc init
Witaj! To polecenie przeprowadzi Cię przez proces konfiguracji.
Proszę przejść do https://oauth.yandex.ru/authorize?response_type=token&client_id= aby uzyskać token OAuth.

Proszę wprowadzić token OAuth:
Proszę wybrać chmurę do wykorzystania:
 [1] cloud-b1gv67ihgfu3bp (id = b1gv67ihgfu3bpt24o0q)
 [2] fevlake-cloud (id = b1g6bvup3toribomnh30)
Proszę wprowadzić swój numer wyboru: 2
Twoja aktualna chmura została ustawiona na 'fevlake-cloud' (id = b1g6bvup3toribomnh30).
Proszę wybrać folder do użycia:
 [1] default (id = b1g5r6h11knotfr8vjp7)
 [2] Utwórz nowy folder
Proszę wprowadzić swój numer wyboru: 1
Twój aktualny folder został ustawiony na 'default' (id = b1g5r6h11knotfr8vjp7).
Czy chcesz skonfigurować domyślną strefę Obliczeniową? [Y/n]
Jaką strefę chcesz użyć jako domyślną profilu?
 [1] ru-central1-a
 [2] ru-central1-b
 [3] ru-central1-c
 [4] Nie ustawiaj domyślnej strefy
Proszę wprowadzić swój numer wyboru: 1
Twoja domyślna strefa Obliczeniowa profilu została ustawiona na 'ru-central1-a'.
vozerov@mba:~ $

W zasadzie proces nie jest skomplikowany — najpierw musisz uzyskać token oauth do zarządzania chmurą, wybrać chmurę i folder, którego będziesz używać.

Jeśli masz kilka kont lub folderów wewnątrz jednej chmury, możesz stworzyć dodatkowe profile z osobnymi ustawieniami za pomocą yc config profile create i przełączać się między nimi.

Oprócz wymienionych powyżej sposobów, zespół Yandex.Cloud napisał bardzo dobry plugin do terraform do zarządzania zasobami chmurowymi. Ze swojej strony przygotowałem repozytorium git, w którym opisałem wszystkie zasoby, które zostaną stworzone w ramach artykułu — https://github.com/rebrainme/yandex-cloud-events/. Interesuje nas gałąź master, więc sklonujmy ją lokalnie:


vozerov@mba:~ $ git clone https://github.com/rebrainme/yandex-cloud-events/ events
Klonowanie do 'events'...
remote: Enumerating objects: 100, done.
remote: Counting objects: 100% (100/100), done.
remote: Compressing objects: 100% (68/68), done.
remote: Total 100 (delta 37), reused 89 (delta 26), pack-reused 0
Odbieranie obiektów: 100% (100/100), 25.65 KiB | 168.00 KiB/s, done.
Rozwiązywanie deltas: 100% (37/37), done.
vozerov@mba:~ $ cd events/terraform/

Wszystkie główne zmienne, które są używane w terraformie, są zapisane w pliku main.tf. Aby rozpocząć pracę, tworzymy w folderze terraform plik private.auto.tfvars o następującej treści:

# Yandex Cloud Oauth token
yc_token = ""
# Yandex Cloud ID
yc_cloud_id = ""
# Yandex Cloud folder ID
yc_folder_id = ""
# Default Yandex Cloud Region
yc_region = "ru-central1-a"
# Cloudflare email
cf_email = ""
# Cloudflare token
cf_token = ""
# Cloudflare zone id
cf_zone_id = ""

Wszystkie zmienne można uzyskać z yc config list, ponieważ narzędzie konsolowe zostało już skonfigurowane. Rekomenduję od razu dodać private.auto.tfvars do .gitignore, aby przypadkowo nie opublikować danych prywatnych.

W private.auto.tfvars również wskazano dane do Cloudflare — do tworzenia rekordów DNS i proxy głównej domeny events.kis.im na nasze serwery. Jeśli nie chcesz używać Cloudflare, usuń inicjalizację dostawcy Cloudflare w main.tf oraz plik dns.tf, który odpowiada za tworzenie niezbędnych rekordów DNS.

W naszej pracy będziemy łączyć wszystkie trzy metody — zarówno interfejs webowy, jak i narzędzie konsolowe, oraz terraform.

Wirtualne sieci

Szczerze mówiąc, ten krok można by pominąć, ponieważ przy tworzeniu nowej chmury automatycznie utworzy się osobna sieć i 3 podsieci — po jednej na każdą strefę dostępności. Jednak chcielibyśmy dla naszego projektu stworzyć osobną sieć z własnym adresowaniem. Ogólny schemat działania sieci w Yandex.Cloud przedstawiony jest na poniższym rysunku (szczerze wzięty z https://cloud.yandex.ru/docs/vpc/concepts/)

Akceptujemy 10 000 wydarzeń w Yandex.Cloud. Część 1

Stworzyłeś wspólną sieć, w której zasoby mogą komunikować się między sobą. Dla każdej strefy dostępności tworzona jest podsieć z jej własnym adresowaniem i łączy się z siecią ogólną. W rezultacie wszystkie zasoby chmurowe w niej mogą się komunikować, nawet będąc w różnych strefach dostępności. Zasoby podłączone do różnych sieci chmurowych mogą widzieć się tylko przez zewnętrzne adresy. Przy okazji, jak ta magia działa w środku, dobrze opisano na Habrze.

Tworzenie sieci opisane jest w pliku network.tf z repozytorium. Tam tworzymy jedną wspólną sieć prywatną internal i podłączamy do niej trzy podsieci w różnych strefach dostępności — internal-a (172.16.1.0/24), internal-b (172.16.2.0/24), internal-c (172.16.3.0/24).

Inicjalizujemy terraform i tworzymy sieci:

vozerov@mba:~\/events\/terraform (master) $ terraform init
... pominięto ...

vozerov@mba:~\/events\/terraform (master) $ terraform apply -target yandex_vpc_subnet.internal-a -target yandex_vpc_subnet.internal-b -target yandex_vpc_subnet.internal-c

... pominięto ...

Plan: 4 do dodania, 0 do zmiany, 0 do zniszczenia.

Czy chcesz wykonać te działania?
  Terraform wykona opisane powyżej działania.
  Tylko 'tak' zostanie zaakceptowane jako zatwierdzenie.

  Wprowadź wartość: tak

yandex_vpc_network.internal: Tworzenie...
yandex_vpc_network.internal: Utworzenie zakończone po 3s [id=enp2g2rhile7gbqlbrkr]
yandex_vpc_subnet.internal-a: Tworzenie...
yandex_vpc_subnet.internal-b: Tworzenie...
yandex_vpc_subnet.internal-c: Tworzenie...
yandex_vpc_subnet.internal-a: Utworzenie zakończone po 6s [id=e9b1dad6mgoj2v4funog]
yandex_vpc_subnet.internal-b: Utworzenie zakończone po 7s [id=e2liv5i4amu52p64ac9p]
yandex_vpc_subnet.internal-c: Wciąż w trakcie tworzenia... [10s minęło]
yandex_vpc_subnet.internal-c: Utworzenie zakończone po 10s [id=b0c2qhsj2vranoc9vhcq]

Zastosowanie zakończone! Zasoby: 4 dodane, 0 zmienione, 0 zniszczone.

Świetnie! Stworzyliśmy naszą sieć i teraz jesteśmy gotowi do tworzenia naszych wewnętrznych usług.

Tworzenie maszyn wirtualnych

Do przetestowania aplikacji wystarczy, że stworzymy dwie maszyny wirtualne — pierwsza będzie nam potrzebna do budowania i uruchamiania aplikacji, druga — do uruchomienia kafka, którą wykorzystamy do przechowywania przychodzących wiadomości. Stworzymy także jedną maszynę, na której skonfigurujemy prometheus do monitorowania aplikacji.

Maszyny wirtualne będą konfigurowane za pomocą ansible, więc przed uruchomieniem terraform upewnij się, że masz jedną z najnowszych wersji ansible. Zainstaluj również wymagane role z ansible galaxy:

vozerov@mba:~\/events\/terraform (master) $ cd ..\/ansible\/\nvozerov@mba:~\/events\/ansible (master) $ ansible-galaxy install -r requirements.yml
- cloudalchemy-prometheus (master) już zainstalowane, pomijanie.
- cloudalchemy-grafana (master) już zainstalowane, pomijanie.
- sansible.kafka (master) już zainstalowane, pomijanie.
- sansible.zookeeper (master) już zainstalowane, pomijanie.
- geerlingguy.docker (master) już zainstalowane, pomijanie.
vozerov@mba:~\/events\/ansible (master) $

W folderze ansible znajduje się przykład pliku konfiguracyjnego .ansible.cfg, którego używam. Może się przydać.

Zanim stworzysz maszyny wirtualne, upewnij się, że uruchomiony jest ssh-agent i klucz ssh został dodany, w przeciwnym razie terraform nie będzie mógł połączyć się z utworzonymi maszynami. Oczywiście natknąłem się na błąd w os x: https://github.com/ansible/ansible/issues/32499#issuecomment-341578864. Aby nie powtórzyła się taka sytuacja, przed uruchomieniem Terraform dodaj małą zmienną do env:

vozerov@mba:~\/events\/terraform (master) $ export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES

W folderze z terraformem tworzymy potrzebne zasoby:

vozerov@mba:~\/events\/terraform (master) $ terraform apply -target yandex_compute_instance.build -target yandex_compute_instance.monitoring -target yandex_compute_instance.kafka
yandex_vpc_network.internal: Odświeżanie stanu... [id=enp2g2rhile7gbqlbrkr]
data.yandex_compute_image.ubuntu_image: Odświeżanie stanu...
yandex_vpc_subnet.internal-a: Odświeżanie stanu... [id=e9b1dad6mgoj2v4funog]

Został wygenerowany plan wykonania, który jest przedstawiony poniżej.
Akcje zasobów są wskazane za pomocą następujących symboli:
  + utworzyć

... pominięto ...

Plan: 3 do dodania, 0 do zmiany, 0 do zniszczenia.

... pominięto ...

Jeśli wszystko zakończyło się pomyślnie (a tak powinno być), to mamy trzy wirtualne maszyny:

  1. build — maszyna do testowania i budowy aplikacji. Docker został zainstalowany automatycznie przez ansible’a.
  2. monitoring — maszyna do monitorowania — zainstalowane są na niej prometheus i grafana. Login / hasło standardowe: admin / admin
  3. kafka — mała maszyna z zainstalowanym kafką, dostępna na porcie 9092.

Sprawdźmy, czy wszystkie one są na miejscu:

vozerov@mba:~\/events (master) $ yc compute instance list
+----------------------+------------+---------------+---------+---------------+-------------+
|          ID          |    NAZWA   |    ZONE ID    | STATUS  |  ZEWNETRZNE IP  | IP WEWNĘTRZNE |
+----------------------+------------+---------------+---------+---------------+-------------+
| fhm081u8bkbqf1pa5kgj | monitoring | ru-central1-a | DZIAŁA | 84.201.159.71 | 172.16.1.35 |
| fhmf37k03oobgu9jmd7p | kafka      | ru-central1-a | DZIAŁA | 84.201.173.41 | 172.16.1.31 |
| fhmt9pl1i8sf7ga6flgp | build      | ru-central1-a | DZIAŁA | 84.201.132.3  | 172.16.1.26 |
+----------------------+------------+---------------+---------+---------------+-------------+

Zasoby są na miejscu, a stąd możemy wyciągnąć ich adresy IP. Potem wszędzie będę używał adresów IP do połączenia przez ssh i testowania aplikacji. Jeśli masz konto na cloudflare, połączone z terraform, — śmiało używaj świeżo utworzonych nazw DNS.
Przy okazji, przy tworzeniu wirtualnej maszyny wydawany jest wewnętrzny adres IP oraz wewnętrzna nazwa DNS, więc można się odnosić do serwerów wewnątrz sieci po nazwach:

ubuntu@build:~$ ping kafka.ru-central1.internal
PING kafka.ru-central1.internal (172.16.1.31) 56(84) bytes of data.
64 bytes from kafka.ru-central1.internal (172.16.1.31): icmp_seq=1 ttl=63 time=1.23 ms
64 bytes from kafka.ru-central1.internal (172.16.1.31): icmp_seq=2 ttl=63 time=0.625 ms
^C
--- statystyki ping dla kafka.ru-central1.internal ---
2 pakiety przesłane, 2 odebrane, 0% utraty pakietów, czas 1001ms
rtt min\/avg\/max\/mdev = 0.625\/0.931\/1.238\/0.308 ms

To będzie nam potrzebne do wskazania aplikacji punktu końcowego z kafką.

Budujemy aplikację

Świetnie, serwery są, aplikacja jest — pozostało tylko ją zbudować i opublikować. Do budowy użyjemy standardowego docker build, a jako repozytorium obrazów weźmiemy usługę od Yandexu — container registry. Ale wszystko po kolei.

Kopiujemy aplikację na maszynę build, wchodzimy przez ssh i budujemy obraz:

vozerov@mba:~\/events\/terraform (master) $ cd ..
vozerov@mba:~\/events (master) $ rsync -av app\/ ubuntu@84.201.132.3:app\/\n\n... pominięto ...\n\nsent 3849 bytes  received 70 bytes  7838.00 bytes\/sec\ntotal size is 3644  speedup is 0.93\n\nvozerov@mba:~\/events (master) $ ssh 84.201.132.3 -l ubuntu\nubuntu@build:~$ cd app\nubuntu@build:~\/app$ sudo docker build -t app .\nWysyłanie kontekstu budowy do demona Docker  6.144kB\nKrok 1\/9 : FROM golang:latest AS build\n... pominięto ...\n\nBudowa zakończona pomyślnie 9760afd8ef65\nPomyślnie oznaczone app:latest

Część pracy wykonana — teraz możemy sprawdzić działanie naszej aplikacji, uruchamiając ją i kierując na kafka:

ubuntu@build:~\/app$ sudo docker run --name app -d -p 8080:8080 app \/app\/app -kafka=kafka.ru-central1.internal:9092<\/code>\n\nZ lokalnej maszyny możemy wysłać testowy event i zobaczyć odpowiedź:\n\n<code>vozerov@mba:~\/events (master) $ curl -D - -s -X POST -d '{"key1":"data1"}' http:\/\/84.201.132.3:8080\/post\nHTTP\/1.1 200 OK\nContent-Type: application\/json\nDate: Mon, 13 Apr 2020 13:53:54 GMT\nContent-Length: 41\n\n{"status":"ok","partition":0,"Offset":0}\nvozerov@mba:~\/events (master) $

Aplikacja odpowiedziała sukcesem zapisu i wskazaniem id partycji oraz offsetu, do którego trafiła wiadomość. Teraz wystarczy stworzyć rejestr w Yandex.Cloud i przesłać tam nasz obraz (jak to zrobić za pomocą trzech linii, opisano w pliku registry.tf). Tworzymy magazyn:

vozerov@mba:~\/events\/terraform (master) $ terraform apply -target yandex_container_registry.events\n\n... pominięto ...\n\nPlan: 1 do dodania, 0 do zmiany, 0 do zniszczenia.\n\n... pominięto ...\n\nZastosowanie zakończone! Zasoby: 1 dodane, 0 zmienione, 0 zniszczone.

Do uwierzytelniania w rejestrze kontenerów jest kilka sposobów — za pomocą tokena oauth, tokena iam lub klucza konta usługi. Więcej szczegółów na ten temat znajduje się w dokumentacji. https://cloud.yandex.ru/docs/container-registry/operations/authentication. Użyjemy klucza konta usługi, dlatego tworzymy konto:

vozerov@mba:~\/events\/terraform (master) $ terraform apply -target yandex_iam_service_account.docker -target yandex_resourcemanager_folder_iam_binding.puller -target yandex_resourcemanager_folder_iam_binding.pusher\n\n... pominięto ...\n\nZastosowanie zakończone! Zasoby: 3 dodane, 0 zmienione, 0 zniszczone.

Teraz wystarczy stworzyć dla niego klucz:

vozerov@mba:~\/events\/terraform (master) $ yc iam key create --service-account-name docker -o key.json\nid: ajej8a06kdfbehbrh91p\nservice_account_id: ajep6d38k895srp9osij\ncreated_at: "2020-04-13T14:00:30Z"\nkey_algorithm: RSA_2048

Otrzymujemy informacje o id naszego magazynu, przesyłamy klucz i autoryzujemy się:

vozerov@mba:~\/events\/terraform (master) $ scp key.json ubuntu@84.201.132.3:\nkey.json                                                                                                                    100% 2392   215.1KB\/s   00:00\n\nvozerov@mba:~\/events\/terraform (master) $ ssh 84.201.132.3 -l ubuntu\n\nubuntu@build:~$ cat key.json | sudo docker login --username json_key --password-stdin cr.yandex\nOSTRZEŻENIE! Twoje hasło będzie przechowywane w formie niezaszyfrowanej w \/home\/ubuntu\/.docker\/config.json.\nSkonfiguruj pomocnika danych uwierzytelniających, aby usunąć to ostrzeżenie. Zobacz\nhttps:\/\/docs.docker.com\/engine\/reference\/commandline\/login\/#credentials-store\n\nLogowanie zakończone sukcesem\nubuntu@build:~$

Aby przesłać obraz do rejestru, będziemy potrzebować ID rejestru kontenerów, uzyskujemy go za pomocą narzędzia yc:

vozerov@mba:~ $ yc container registry get events
id: crpdgj6c9umdhgaqjfmm
folder_id:
name: events
status: ACTIVE
created_at: "2020-04-13T13:56:41.914Z"

Następnie oznaczamy nasz obraz nową nazwą i przesyłamy go:

ubuntu@build:~$ sudo docker tag app cr.yandex/crpdgj6c9umdhgaqjfmm/events:v1
ubuntu@build:~$ sudo docker push cr.yandex/crpdgj6c9umdhgaqjfmm/events:v1
Przesyłanie odnosi się do repozytorium [cr.yandex/crpdgj6c9umdhgaqjfmm/events]
8c286e154c6e: Wysłano
477c318b05cb: Wysłano
beee9f30bc1f: Wysłano
v1: skrót: sha256:1dd5aaa9dbdde2f60d833be0bed1c352724be3ea3158bcac3cdee41d47c5e380 rozmiar: 946

Możemy upewnić się, że obraz został pomyślnie przesłany:

vozerov@mba:~/events/terraform (master) $ yc container repository list
+----------------------+-----------------------------+
|          ID          |            NAME             |
+----------------------+-----------------------------+
| crpe8mqtrgmuq07accvn | crpdgj6c9umdhgaqjfmm/events |
+----------------------+-----------------------------+

Przy okazji, jeśli zainstalujesz narzędzie yc na maszynie z linuxem, możesz użyć polecenia

yc container registry configure-docker

do konfiguracji dockera.

Podsumowanie

Wykonaliśmy dużą i trudną pracę i w rezultacie:

  1. Opracowaliśmy architekturę naszego przyszłego serwisu.
  2. Napisaliśmy aplikację w golang, która wdraża naszą logikę biznesową.
  3. Zbudowaliśmy ją i wgraliśmy do prywatnego rejestru kontenerów.

W następnej części przejdziemy do ciekawego momentu — załadujemy naszą aplikację na produkcję i w końcu uruchomimy na nią obciążenie. Nie przełączaj się!

Ten materiał jest dostępny w nagraniu wideo z otwartego praktykuma REBRAIN & Yandex.Cloud: Przyjmujemy 10 000 zapytań na sekundę w Yandex Cloud — https://youtu.be/cZLezUm0ekE

Jeśli jesteś zainteresowany uczestnictwem w takich wydarzeniach online i zadawaniem pytań w czasie rzeczywistym, dołącz do kanału DevOps by REBRAIN.

Szczególne podziękowania dla Yandex.Cloud za możliwość organizacji takiego wydarzenia. Link do nich — https://cloud.yandex.ru/prices

Jeśli potrzebujesz migracji do chmury lub masz pytania dotyczące swojej infrastruktury, śmiało zostawiaj zgłoszenie.

P.S. Mamy dwa bezpłatne audyty miesięcznie, być może to właśnie Twój projekt znajdzie się w ich gronie.

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster