Wir akzeptieren 10.000 Events in Yandex.Cloud. Teil 1

Hallo zusammen, Freunde!

* Dieser Artikel basiert auf dem offenen Praktikum von REBRAIN & Yandex.Cloud. Wenn Sie lieber Videos anschauen, können Sie es unter diesem Link finden — https://youtu.be/cZLezUm0ekE

Kürzlich hatten wir die Möglichkeit, Yandex.Cloud live auszuprobieren. Da wir es gründlich und ausführlich erfahren wollten, haben wir sofort von der Idee Abstand genommen, einen einfachen Wordpress-Blog mit einer Cloud-Datenbank zu starten – viel zu langweilig. Nach kurzer Überlegung haben wir beschlossen, etwas Ähnliches wie die Produktionsarchitektur eines Dienstes für den Empfang und die Analyse von Ereignissen in nahezu Echtzeit zu entwickeln.

Ich bin mir absolut sicher, dass die überwältigende Mehrheit der Online (und nicht nur) Unternehmen in irgendeiner Form eine Menge Informationen über ihre Benutzer und deren Aktivitäten sammelt. Mindestens ist das notwendig, um bestimmte Entscheidungen zu treffen – zum Beispiel, wenn Sie ein Online-Spiel betreiben – dann können Sie sich die Statistiken ansehen, auf welchem Niveau die Benutzer am häufigsten stecken bleiben und Ihr Spiel deinstallieren. Oder warum Benutzer Ihre Seite verlassen, ohne etwas gekauft zu haben (Hallo, Yandex.Metrika).

Also, unsere Geschichte: wie wir eine Anwendung auf Golang geschrieben, Kafka vs RabbitMQ vs YQS getestet, Datenstreaming in ein Clickhouse-Cluster geschrieben und Daten mit Yandex Datalens visualisiert haben. Natürlich wurde all dies mit infrastrukturellen Belangen in Form von Docker, Terraform, GitLab CI und natürlich Prometheus gewürzt. Lass uns anfangen!

Zunächst möchte ich vorwegnehmen, dass wir alles nicht in einem Rutsch einrichten können – dafür benötigen wir mehrere Artikel in dieser Serie. Ein wenig über die Struktur:

Teil 1 (den Sie gerade lesen). Wir werden die Anforderungen und die Architektur der Lösung festlegen und eine Anwendung auf Golang schreiben.
Teil 2. Wir bringen unsere Anwendung in die Produktion, machen sie skalierbar und testen die Belastung.
Teil 3. Wir versuchen zu verstehen, warum wir Nachrichten im Buffer speichern sollten und nicht in Dateien, und vergleichen Kafka, RabbitMQ und den Yandex Queue Service miteinander.
Teil 4. Wir werden ein Clickhouse-Cluster einrichten, Datenstreaming für den Transfer von Daten aus dem Buffer dorthin schreiben und die Visualisierung in Datalens einrichten.
Teil 5. Wir bringen die gesamte Infrastruktur auf den neuesten Stand – wir richten CI/CD mit GitLab CI ein, verbinden das Monitoring und die Service Discovery mit Prometheus und Consul.

Technische Anforderungen

Zuerst formulieren wir die technischen Anforderungen – was genau wir am Ende erhalten möchten.

  1. Wir möchten einen Endpoint mit dem Namen events.kis.im haben (kis.im ist die Testdomäne, die wir in allen Artikeln verwenden werden), der Ereignisse über HTTPS empfangen soll.
  2. Ereignisse sind ein einfaches JSON in der Form: {"event": "view", "os": "linux", "browser": "chrome"}. In der finalen Phase fügen wir etwas mehr Felder hinzu, aber das wird nicht von großer Bedeutung sein. Wenn gewünscht, kann man auf Protobuf umsteigen.
  3. Der Dienst sollte in der Lage sein, 10.000 Ereignisse pro Sekunde zu verarbeiten.
  4. Es sollte die Möglichkeit bestehen, horizontal zu skalieren – einfach durch das Hinzufügen neuer Instanzen zu unserer Lösung. Es wäre auch nicht schlecht, wenn wir den Frontend-Teil in verschiedene geografische Standorte verlagern könnten, um die Latenz bei den Kundenanfragen zu reduzieren.
  5. Ausfallsicherheit. Die Lösung sollte stabil genug sein und in der Lage sein, bei Ausfällen beliebiger Teile (bis zu einer bestimmten Anzahl, natürlich) zu überleben.

Architektur

Für solche Aufgaben existieren schon lange klassische Architekturen, die eine effektive Skalierbarkeit ermöglichen. Im Bild ist ein Beispiel unserer Lösung dargestellt.

Wir akzeptieren 10.000 Events in Yandex.Cloud. Teil 1

Also, was haben wir:

1. Auf der linken Seite sind unsere Geräte abgebildet, die verschiedene Ereignisse generieren, sei es das Abschließen von Leveln in einem Spiel auf dem Smartphone oder das Erstellen einer Bestellung in einem Online-Shop über einen herkömmlichen Browser. Ein Ereignis, wie im Lastenheft angegeben, ist ein einfaches JSON, das an unseren Endpoint – events.kis.im – gesendet wird.

2. Die ersten beiden Server sind einfache Lastverteiler, ihre Hauptaufgaben sind:

  • Ständig verfügbar zu sein. Dazu kann man beispielsweise keepalived verwenden, das die virtuelle IP im Fall von Problemen zwischen den Knoten umschaltet.
  • TLS zu terminieren. Ja, das TLS werden wir tatsächlich dort terminieren. Erstens, um sicherzustellen, dass unsere Lösung den Anforderungen des Lastenhefts entspricht, und zweitens, um die Last der Einrichtung einer verschlüsselten Verbindung von unseren Backend-Servern zu nehmen.
  • Eingehende Anfragen an verfügbare Backend-Server zu balancieren. Das Schlüsselwort hier ist – verfügbar. Daraus ergeben sich die Anforderungen, dass Lastverteiler in der Lage sein müssen, unsere Server mit Anwendungen zu überwachen und den Traffic nicht an ausgefallene Knoten zu balancieren.

3. Hinter den Lastverteilern folgen unsere Anwendungsserver, auf denen eine recht einfache Anwendung läuft. Sie sollte in der Lage sein, eingehende HTTP-Anfragen entgegenzunehmen, das gesendete JSON zu validieren und die Daten in einen Puffer zu speichern.

4. Als Puffer ist auf dem Schema kafka dargestellt, obwohl natürlich auf diesem Level auch andere ähnliche Dienste verwendet werden können. Wir werden Kafka, rabbitmq und yqs im dritten Artikel vergleichen.

5. Der vorletzte Punkt unserer Architektur ist Clickhouse – eine spaltenbasierte Datenbank, die es ermöglicht, riesige Datenmengen zu speichern und zu verarbeiten. Auf dieser Ebene müssen wir die Daten aus dem Puffer in das tatsächliche Speichersystem übertragen (darüber wird im 4. Artikel berichtet).

Ein solches Schema ermöglicht es uns, jede Schicht unabhängig horizontal zu skalieren. Wenn die Backend-Server überlastet sind – fügen wir einfach weitere hinzu – da sie stateless Anwendungen sind, ist dies jederzeit auch automatisch möglich. Wenn der Puffer in Form von Kafka nicht mehr ausreicht – fügen wir mehr Server hinzu und übertragen einen Teil der Partitionen unseres Themas darauf. Wenn Clickhouse nicht mehr mitkommt – das ist unmöglich 🙂 In der Tat fügen wir ebenfalls Server hinzu und sharden die Daten.

Übrigens, falls Sie den optionalen Teil unseres Lastenhefts umsetzen und die Skalierung an verschiedenen Geolokationen durchführen möchten, ist das ganz einfach:

Wir akzeptieren 10.000 Events in Yandex.Cloud. Teil 1

In jeder Geolokation setzen wir einen Load Balancer mit der Anwendung und Kafka auf. Insgesamt sind 2 Anwendungsserver, 3 Kafka-Nodes und ein Cloud-Load-Balancer, beispielsweise Cloudflare, ausreichend, der die Verfügbarkeit der Anwendungs-Nodes überprüft und die Anfragen basierend auf der ursprünglichen IP-Adresse des Clients geolokalisiert ausbalanciert. So landen die von einem amerikanischen Kunden gesendeten Daten auf amerikanischen Servern. Die Daten aus Afrika landen hingegen auf afrikanischen Servern.

Was als nächstes kommt, ist ganz einfach – wir verwenden das Mirror-Tool aus dem Kafka-Set und kopieren alle Daten aus allen Standorten in unser zentrales Rechenzentrum in Russland. Dort zerlegen wir die Daten und speichern sie in Clickhouse für die anschließende Visualisierung.

So, wir haben die Architektur verstanden – jetzt fangen wir an, Yandex.Cloud auszutesten!

Wir schreiben eine Anwendung

Bis zur Cloud müssen wir noch ein wenig Geduld haben und einen relativ einfachen Dienst zur Verarbeitung eingehender Ereignisse schreiben. Wir werden Golang verwenden, da es sich als sehr gut geeignet als Programmiersprache für die Entwicklung von Netzwerkapplikationen erwiesen hat.

Nachdem wir eine Stunde (vielleicht auch zwei) Zeit investiert haben, erhalten wir etwa so etwas: https://github.com/RebrainMe/yandex-cloud-events/blob/master/app/main.go.

Welche wesentlichen Punkte möchte ich hier hervorheben:

1. Beim Start der Anwendung können zwei Flags angegeben werden. Eines steuert den Port, an dem wir eingehende HTTP-Anfragen abhören (-addr). Das andere – die Adresse des Kafka-Servers, an den wir unsere Ereignisse schreiben werden (-kafka):

addr     = flag.String("addr", ":8080", "TCP-Adresse, auf die gehört werden soll")
kafka    = flag.String("kafka", "127.0.0.1:9092", "Kafka-Endpunkte")

2. Die Anwendung verwendet die Bibliothek sarama ([] github.com/Shopify/sarama) zum Senden von Nachrichten an das Kafka-Cluster. Wir haben die Einstellungen sofort auf maximale Verarbeitungsgeschwindigkeit ausgelegt:

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

3. Außerdem ist in unsere Anwendung ein Prometheus-Client integriert, der verschiedene Metriken sammelt, wie zum Beispiel:

  • die Anzahl der Anfragen an unsere Anwendung;
  • die Anzahl der Fehler bei der Anfragebearbeitung (konnte POST-Anfrage nicht lesen, fehlerhaftes JSON, konnte nicht in Kafka schreiben);
  • die Verarbeitungszeit einer Anfrage vom Client, einschließlich der Zeit zum Schreiben der Nachricht in Kafka.

4. Drei Endpunkte, die unsere Anwendung verarbeitet:

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

Ich möchte anmerken, dass der Code nicht perfekt ist – er kann (und sollte!) verbessert werden. Zum Beispiel könnte man auf die Verwendung von net/http verzichten und auf das schnellere fasthttp umsteigen. Oder man könnte die Verarbeitungszeit und die CPU-Ressourcen sparen, indem die Validierung des JSON auf einen späteren Zeitpunkt verschoben wird – wenn die Daten von dem Puffer in das ClickHouse-Cluster übertragen werden.

Neben der entwicklerischen Seite haben wir auch sofort an unsere zukünftige Infrastruktur gedacht und entschieden, unsere Anwendung über Docker bereitzustellen. Das endgültige Dockerfile zum Erstellen der Anwendung – https://github.com/RebrainMe/yandex-cloud-events/blob/master/app/Dockerfile. Insgesamt ist es relativ einfach, der einzige Punkt, auf den ich hinweisen möchte, ist das Multistage-Build, das es ermöglicht, das endgültige Image unseres Containers zu verringern.

Erste Schritte in der Cloud

Zuerst registrieren wir uns unter cloud.yandex.ru. Nach dem Ausfüllen aller erforderlichen Felder wird für uns ein Konto erstellt und wir erhalten einen Zuschuss in einer bestimmten Geldsumme, den wir zur Testung der Cloud-Dienste verwenden können. Wenn Sie alle Schritte aus unserem Artikel wiederholen möchten, sollte dieser Zuschuss ausreichen.

Nach der Registrierung wird für Sie eine separate Cloud und ein Standardkatalog erstellt, in dem Sie mit der Erstellung von Cloud-Ressourcen beginnen können. Im Allgemeinen sieht die Beziehung der Ressourcen in Yandex.Cloud folgendermaßen aus:

Wir akzeptieren 10.000 Events in Yandex.Cloud. Teil 1

Auf einem Konto können Sie mehrere Clouds erstellen. Innerhalb der Cloud können verschiedene Verzeichnisse für verschiedene Unternehmensprojekte angelegt werden. Weitere Informationen hierzu finden Sie in der Dokumentation — https://cloud.yandex.ru/docs/resource-manager/concepts/resources-hierarchy. Übrigens werde ich im weiteren Verlauf des Textes häufig darauf verweisen. Als ich die gesamte Infrastruktur von Grund auf aufbaute, hat mir die Dokumentation mehr als einmal geholfen, deshalb empfehle ich, sie gründlich zu studieren.

Zur Verwaltung der Cloud können sowohl die Web-Oberfläche als auch das Konsolenprogramm — yc verwendet werden. Die Installation erfolgt mit einem einzigen Befehl (für Linux und Mac OS):

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

Wenn Sie sich wegen des Ausführens von Skripten aus dem Internet unsicher sind, können Sie das Skript zuerst öffnen und lesen. Außerdem führen wir es unter unserem eigenen Benutzer ohne Root-Rechte aus.

Wenn Sie den Client für Windows installieren möchten, können Sie die Anleitung nutzen hier und anschließend yc init, um ihn vollständig einzurichten:

vozerov@mba:~ $ yc init
Willkommen! Dieser Befehl führt Sie durch den Konfigurationsprozess.
Bitte gehen Sie zu https://oauth.yandex.ru/authorize?response_type=token&client_id= um ein OAuth-Token zu erhalten.

Bitte geben Sie das OAuth-Token ein:
Bitte wählen Sie die Cloud, die Sie verwenden möchten:
 [1] cloud-b1gv67ihgfu3bp (id = b1gv67ihgfu3bpt24o0q)
 [2] fevlake-cloud (id = b1g6bvup3toribomnh30)
Bitte geben Sie Ihre numerische Wahl ein: 2
Ihre aktuelle Cloud wurde auf 'fevlake-cloud' (id = b1g6bvup3toribomnh30) eingestellt.
Bitte wählen Sie den Ordner, den Sie verwenden möchten:
 [1] default (id = b1g5r6h11knotfr8vjp7)
 [2] Neuen Ordner erstellen
Bitte geben Sie Ihre numerische Wahl ein: 1
Ihr aktueller Ordner wurde auf 'default' (id = b1g5r6h11knotfr8vjp7) eingestellt.
Möchten Sie eine Standard-Compute-Zone konfigurieren? [Y/n]
Welche Zone möchten Sie als Standardprofil verwenden?
 [1] ru-central1-a
 [2] ru-central1-b
 [3] ru-central1-c
 [4] Keine Standardzone festlegen
Bitte geben Sie Ihre numerische Wahl ein: 1
Ihre Standard-Compute-Zone wurde auf 'ru-central1-a' gesetzt.
vozerov@mba:~ $

Im Grunde ist der Prozess nicht kompliziert — zuerst müssen Sie ein OAuth-Token zur Verwaltung der Cloud erhalten, dann die Cloud und den Ordner auswählen, den Sie verwenden möchten.

Wenn Sie mehrere Konten oder Ordner innerhalb einer Cloud haben, können Sie zusätzliche Profile mit eigenen Einstellungen über yc config profile create erstellen und zwischen ihnen wechseln.

Neben den oben genannten Methoden hat das Yandex.Cloud-Team ein sehr gutes Plugin für Terraform zur Verwaltung von Cloud-Ressourcen erstellt. Von meiner Seite habe ich ein Git-Repository vorbereitet, in dem ich alle Ressourcen beschrieben habe, die im Rahmen dieses Artikels erstellt werden — https://github.com/rebrainme/yandex-cloud-events/. Uns interessiert der master-Zweig, lassen Sie uns diesen lokal klonen:


vozerov@mba:~ $ git clone https://github.com/rebrainme/yandex-cloud-events/ events
Klone in 'events'...
remote: Objekte auflisten: 100, done.
remote: Objekte zählen: 100% (100/100), done.
remote: Objekte komprimieren: 100% (68/68), done.
remote: Insgesamt 100 (delta 37), wiederverwendet 89 (delta 26), pack-reused 0
Empfangene Objekte: 100% (100/100), 25.65 KiB | 168.00 KiB/s, done.
Deltas auflösen: 100% (37/37), done.
vozerov@mba:~ $ cd events/terraform/

Alle Hauptvariablen, die in Terraform verwendet werden, sind in der Datei main.tf festgelegt. Um zu beginnen, erstellen wir in dem Ordner terraform die Datei private.auto.tfvars mit folgendem Inhalt:

# 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 = ""

Alle Variablen können aus yc config list entnommen werden, da wir das Konsolenprogramm bereits konfiguriert haben. Ich empfehle, private.auto.tfvars sofort zu .gitignore hinzuzufügen, um private Daten nicht versehentlich zu veröffentlichen.

In private.auto.tfvars haben wir auch die Daten von Cloudflare angegeben – zur Erstellung von DNS-Einträgen und zum Proxieren der Hauptdomain events.kis.im auf unsere Server. Wenn Sie Cloudflare nicht verwenden möchten, entfernen Sie die Initialisierung des Cloudflare-Providers aus main.tf und die Datei dns.tf, die für die Erstellung der erforderlichen DNS-Einträge zuständig ist.

Wir werden in unserer Arbeit alle drei Methoden kombinieren – das Web-Interface, das Konsolenprogramm und Terraform.

Virtuelle Netzwerke

Ehrlich gesagt könnte dieser Schritt übersprungen werden, da bei der Erstellung einer neuen Cloud automatisch ein separates Netzwerk und 3 Subnetze – eines für jede Verfügbarkeitszone – erstellt werden. Aber dennoch möchten wir für unser Projekt ein separates Netzwerk mit eigener Adressierung erstellen. Das allgemeine Schema für die Funktionsweise des Netzwerks in Yandex.Cloud ist in der folgenden Abbildung dargestellt (ehrlich von https://cloud.yandex.ru/docs/vpc/concepts/)

Wir akzeptieren 10.000 Events in Yandex.Cloud. Teil 1

Also, Sie erstellen ein gemeinsames Netzwerk, innerhalb dessen Ressourcen miteinander kommunizieren können. Für jede Verfügbarkeitszone wird ein Subnetz mit eigener Adressierung erstellt und mit dem gemeinsamen Netzwerk verbunden. Dadurch können alle Cloud-Ressourcen in diesem Netzwerk kommunizieren, auch wenn sie sich in unterschiedlichen Verfügbarkeitszonen befinden. Ressourcen, die mit verschiedenen Cloud-Netzwerken verbunden sind, können sich nur über externe Adressen sehen. Übrigens, wie diese Magie im Inneren funktioniert, wurde gut auf Habré beschrieben..

Die Erstellung des Netzwerks ist in der Datei network.tf im Repository beschrieben. Dort erstellen wir ein gemeinsames privates Netzwerk internal und verbinden es mit drei Subnetzen in verschiedenen Verfügbarkeitszonen – internal-a (172.16.1.0/24), internal-b (172.16.2.0/24), internal-c (172.16.3.0/24).

Wir initialisieren Terraform und erstellen die Netzwerke:

vozerov@mba:~\/events\/terraform (master) $ terraform init
... übersprungen ...

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

... übersprungen ...

Plan: 4 hinzuzufügende, 0 zu ändernde, 0 zu zerstörende Ressourcen.

Möchten Sie diese Aktionen ausführen?
  Terraform wird die oben beschriebenen Aktionen ausführen.
  Nur 'ja' wird akzeptiert, um die Genehmigung zu erteilen.

  Geben Sie einen Wert ein: ja

yandex_vpc_network.internal: Wird erstellt...
yandex_vpc_network.internal: Erstellung nach 3s abgeschlossen [id=enp2g2rhile7gbqlbrkr]
yandex_vpc_subnet.internal-a: Wird erstellt...
yandex_vpc_subnet.internal-b: Wird erstellt...
yandex_vpc_subnet.internal-c: Wird erstellt...
yandex_vpc_subnet.internal-a: Erstellung nach 6s abgeschlossen [id=e9b1dad6mgoj2v4funog]
yandex_vpc_subnet.internal-b: Erstellung nach 7s abgeschlossen [id=e2liv5i4amu52p64ac9p]
yandex_vpc_subnet.internal-c: Wird noch erstellt... [10s vergangen]
yandex_vpc_subnet.internal-c: Erstellung nach 10s abgeschlossen [id=b0c2qhsj2vranoc9vhcq]

Anwendung abgeschlossen! Ressourcen: 4 hinzugefügt, 0 geändert, 0 zerstört.

Ausgezeichnet! Wir haben unser Netzwerk erstellt und sind nun bereit, unsere internen Dienste zu erstellen.

Erstellen von virtuellen Maschinen

Für die Testung der Anwendung wird es ausreichend sein, zwei virtuelle Maschinen zu erstellen – die erste benötigen wir zum Bauen und Ausführen der Anwendung, die zweite wird für die Ausführung von Kafka verwendet, das wir zur Speicherung von eingehenden Nachrichten nutzen werden. Außerdem erstellen wir noch eine weitere Maschine, auf der wir Prometheus zur Überwachung der Anwendung einrichten.

Die virtuellen Maschinen werden mit Ansible konfiguriert, stellen Sie also sicher, dass vor dem Start von Terraform eine der neuesten Ansible-Versionen installiert ist. Und installieren Sie die erforderlichen Rollen von Ansible Galaxy:

vozerov@mba:~\/events\/terraform (master) $ cd ..\/ansible\/ 
vozerov@mba:~\/events\/ansible (master) $ ansible-galaxy install -r requirements.yml
- cloudalchemy-prometheus (master) ist bereits installiert, wird übersprungen.
- cloudalchemy-grafana (master) ist bereits installiert, wird übersprungen.
- sansible.kafka (master) ist bereits installiert, wird übersprungen.
- sansible.zookeeper (master) ist bereits installiert, wird übersprungen.
- geerlingguy.docker (master) ist bereits installiert, wird übersprungen.
vozerov@mba:~\/events\/ansible (master) $

Im Ansible-Ordner gibt es ein Beispiel einer Konfigurationsdatei .ansible.cfg, die ich verwende. Vielleicht ist das nützlich.

Bevor Sie virtuelle Maschinen erstellen, stellen Sie sicher, dass der SSH-Agent läuft und der SSH-Schlüssel hinzugefügt wurde, sonst kann Terraform keine Verbindung zu den erstellten Maschinen herstellen. Ich bin natürlich auf einen Bug in OS X gestoßen: https://github.com/ansible/ansible/issues/32499#issuecomment-341578864. Damit Ihnen nicht das Gleiche passiert, fügen Sie vor dem Start von Terraform eine kleine Variable in die Umgebungsvariablen hinzu:

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

Im Terraform-Ordner erstellen wir die erforderlichen Ressourcen:

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: Zustand wird aktualisiert... [id=enp2g2rhile7gbqlbrkr]
data.yandex_compute_image.ubuntu_image: Zustand wird aktualisiert...
yandex_vpc_subnet.internal-a: Zustand wird aktualisiert... [id=e9b1dad6mgoj2v4funog]

Ein Ausführungsplan wurde erstellt und wird unten angezeigt.
Ressourcenaktionen sind mit den folgenden Symbolen gekennzeichnet:
  + erstellen

... übersprungen ...

Plan: 3 hinzuzufügen, 0 zu ändern, 0 zu löschen.

... übersprungen ...

Wenn alles erfolgreich war (was der Fall sein sollte), haben wir drei virtuelle Maschinen:

  1. build — eine Maschine zum Testen und Erstellen der Anwendung. Docker wurde automatisch von ansible installiert.
  2. monitoring — eine Maschine für das Monitoring — darauf sind prometheus & grafana installiert. Login / Passwort ist standardmäßig: admin / admin
  3. kafka — eine kleine Maschine mit installiertem kafka, erreichbar über Port 9092.

Überprüfen wir, ob alle vorhanden sind:

vozerov@mba:~\/events (master) $ yc compute instance list
+----------------------+------------+---------------+---------+---------------+-------------+
|          ID          |    NAME    |    ZONE ID    | STATUS  |  EXTERNAL IP  | INTERNAL IP |
+----------------------+------------+---------------+---------+---------------+-------------+
| fhm081u8bkbqf1pa5kgj | monitoring | ru-central1-a | RUNNING | 84.201.159.71 | 172.16.1.35 |
| fhmf37k03oobgu9jmd7p | kafka      | ru-central1-a | RUNNING | 84.201.173.41 | 172.16.1.31 |
| fhmt9pl1i8sf7ga6flgp | build      | ru-central1-a | RUNNING | 84.201.132.3  | 172.16.1.26 |
+----------------------+------------+---------------+---------+---------------+-------------+

Die Ressourcen sind vorhanden, und von hier aus können wir ihre IP-Adressen abrufen. Im Folgenden werde ich die IP-Adressen zum SSH-Zugriff und zur Testung der Anwendung verwenden. Wenn Sie ein bei cloudflare angemeldetes Konto haben, das mit terraform verbunden ist, verwenden Sie ruhig die frisch erstellten DNS-Namen.
Übrigens wird bei der Erstellung einer virtuellen Maschine eine interne IP und ein internes DNS-Name bereitgestellt, sodass man innerhalb des Netzwerks mit Namen auf die Server zugreifen kann:

ubuntu@build:~$ ping kafka.ru-central1.internal
PING kafka.ru-central1.internal (172.16.1.31) 56(84) bytes of data.
64 bytes von kafka.ru-central1.internal (172.16.1.31): icmp_seq=1 ttl=63 Zeit=1.23 ms
64 bytes von kafka.ru-central1.internal (172.16.1.31): icmp_seq=2 ttl=63 Zeit=0.625 ms
^C
--- kafka.ru-central1.internal ping statistics ---
2 Pakete gesendet, 2 empfangen, 0% Paketverlust, Zeit 1001ms
rtt min\/avg\/max\/mdev = 0.625\/0.931\/1.238\/0.308 ms

Das wird uns nützlich sein, um dem Anwendung den Endpunkt für kafka anzugeben.

Anwendung erstellen

Perfekt, die Server sind da, die Anwendung ist vorhanden — wir müssen sie nur noch erstellen und veröffentlichen. Zum Erstellen verwenden wir den normalen docker build, und als Imagespeicher nehmen wir den Service von Yandex — container registry. Aber alles der Reihe nach.

Kopieren wir die Anwendung auf die build-Maschine, melden uns per SSH an und erstellen das Image:

vozerov@mba:~\/events\/terraform (master) $ cd ..
vozerov@mba:~\/events (master) $ rsync -av app\/ ubuntu@84.201.132.3:app\/ 

... übersprungen ...

sent 3849 bytes  received 70 bytes  7838.00 bytes\/sec
total size is 3644  speedup is 0.93

vozerov@mba:~\/events (master) $ ssh 84.201.132.3 -l ubuntu
ubuntu@build:~$ cd app
ubuntu@build:~\/app$ sudo docker build -t app .
Sending build context to Docker daemon  6.144kB
Step 1\/9 : FROM golang:latest AS build
... übersprungen ...

Erfolgreich gebaut 9760afd8ef65
Erfolgreich getaggt app:latest

Die Hälfte der Arbeit ist erledigt — jetzt können wir die Funktionsfähigkeit unserer Anwendung überprüfen, indem wir sie starten und an kafka senden:

ubuntu@build:~\/app$ sudo docker run --name app -d -p 8080:8080 app \/app\/app -kafka=kafka.ru-central1.internal:9092 

Von der lokalen Maschine aus können wir ein Test-Event senden und die Antwort sehen:

vozerov@mba:~\/events (master) $ curl -D - -s -X POST -d '{"key1":"data1"}' http:\/\/84.201.132.3:8080\/post
HTTP\/1.1 200 OK
Content-Type: application\/json
Date: Mon, 13 Apr 2020 13:53:54 GMT
Content-Length: 41

{"status":"ok","partition":0,"Offset":0}
vozerov@mba:~\/events (master) $

Die App hat erfolgreich registriert und die ID der Partition sowie den Offset angegeben, in den die Nachricht eingegangen ist. Jetzt ist es am besten — ein Registry in Yandex.Cloud zu erstellen und unser Image dort hochzuladen (wie man das in drei Zeilen macht, steht in der registry.tf-Datei). Wir erstellen ein Repository:

vozerov@mba:~\/events\/terraform (master) $ terraform apply -target yandex_container_registry.events

... übersprungen ...

Plan: 1 hinzuzufügen, 0 zu ändern, 0 zu zerstören.

... übersprungen ...

Anwendung abgeschlossen! Ressourcen: 1 hinzugefügt, 0 geändert, 0 zerstört.

Es gibt mehrere Möglichkeiten zur Authentifizierung im Container-Registry — mit einem OAuth-Token, IAM-Token oder und einem Service-Account-Schlüssel. Nähere Informationen zu diesen Methoden finden Sie in der Dokumentation. https://cloud.yandex.ru/docs/container-registry/operations/authenticationWir werden den Schlüssel des Dienstkontos verwenden, also erstellen wir ein 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

... übersprungen ...

Anwendung abgeschlossen! Ressourcen: 3 hinzugefügt, 0 geändert, 0 zerstört.

Jetzt müssen wir einen Schlüssel für ihn erstellen:

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

Wir erhalten Informationen über die ID unseres Repositories, übertragen den Schlüssel und authentifizieren uns:

vozerov@mba:~\/events\/terraform (master) $ scp key.json ubuntu@84.201.132.3:
key.json                                                                                                                    100% 2392   215.1KB\/s   00:00

vozerov@mba:~\/events\/terraform (master) $ ssh 84.201.132.3 -l ubuntu

ubuntu@build:~$ cat key.json | sudo docker login --username json_key --password-stdin cr.yandex
WARNING! Ihr Passwort wird unverschlüsselt in \/home\/ubuntu\/.docker\/config.json gespeichert.
Konfigurieren Sie einen Credential Helper, um diese Warnung zu entfernen. Siehe
https:\/\/docs.docker.com\/engine\/reference\/commandline\/login\/#credentials-store

Anmeldung erfolgreich
ubuntu@build:~$

Für das Hochladen des Images in das Registry benötigen wir die ID des Container-Registrys, die wir mit dem yc-Tool abrufen.

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

Nach diesem Schritt taggen wir unser Image mit einem neuen Namen und laden es hoch:

ubuntu@build:~$ sudo docker tag app cr.yandex/crpdgj6c9umdhgaqjfmm/events:v1
ubuntu@build:~$ sudo docker push cr.yandex/crpdgj6c9umdhgaqjfmm/events:v1
Der Push verweist auf das Repository [cr.yandex/crpdgj6c9umdhgaqjfmm/events]
8c286e154c6e: Hochgeladen
477c318b05cb: Hochgeladen
beee9f30bc1f: Hochgeladen
v1: Digest: sha256:1dd5aaa9dbdde2f60d833be0bed1c352724be3ea3158bcac3cdee41d47c5e380 Größe: 946

Wir können sicherstellen, dass das Image erfolgreich hochgeladen wurde:

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

Übrigens, wenn Sie das Tool yc auf einem Linux-Rechner installieren, können Sie den Befehl verwenden:

yc container registry configure-docker

um Docker zu konfigurieren.

Fazit

Wir haben viel harte Arbeit geleistet und das Ergebnis ist:

  1. Wir haben die Architektur unseres zukünftigen Dienstes entworfen.
  2. Wir haben eine Anwendung in Golang geschrieben, die unsere Geschäftslogik umsetzt.
  3. Wir haben sie zusammengestellt und in ein privates Container-Registry übertragen.

Im nächsten Teil kommen wir zu etwas Interessantem — wir werden unsere Anwendung in die Produktion überführen und schließlich eine Last darauf ausüben. Bleiben Sie dran!

Dieses Material ist auch als Videoaufzeichnung des offenen Praktikums REBRAIN & Yandex.Cloud vorhanden: 10.000 Anfragen pro Sekunde auf Yandex Cloud empfangen — https://youtu.be/cZLezUm0ekE

Wenn Sie daran interessiert sind, an solchen Veranstaltungen online teilzunehmen und Fragen in Echtzeit zu stellen, schließen Sie sich an: Kanal DevOps von REBRAIN.

Ein besonderer Dank geht an Yandex.Cloud für die Möglichkeit, eine solche Veranstaltung durchzuführen. Hier ist der Link zu ihnen — https://cloud.yandex.ru/prices

Wenn Sie einen Umzug in die Cloud benötigen oder Fragen zu Ihrer Infrastruktur haben, lassen Sie gerne eine Anfrage da.

P.S. Wir haben zwei kostenlose Audits pro Monat, möglicherweise ist genau Ihr Projekt eines davon.

Quelle: habr.com

60GB SSD 8Gb DDR4