Es ist Zeit, die Container in Angriff zu nehmen
Zunächst verwenden wir das neueste Linux Alpine-Image. Linux Alpine ist eine leichtgewichtige Linux-Distribution, die für die Ausführung von Webanwendungen in Docker entwickelt und optimiert wurde. Mit anderen Worten, Linux Alpine verfügt über genügend Abhängigkeiten und Funktionen, um die meisten Anwendungen auszuführen. Dies bedeutet, dass die Bildgröße ca. 8 MB beträgt!
Im Vergleich zu beispielsweise einer virtuellen Ubuntu-Maschine mit einer Kapazität von etwa 1 GB sind Docker-Images für Microservices und Cloud Computing natürlicher geworden.
Nun hoffe ich, dass Sie Wert in der Containerisierung sehen und wir mit dem „Dockerisieren“ unseres ersten Dienstes beginnen können. Erstellen wir einen Dockerfile
$ touch-Sendungsdienst / Dockerfile .
Erster TeilOriginales EwanValentine-RepositoryOriginalartikelFügen Sie in der Docker-Datei Folgendes hinzu:
FROM alpine:latest RUN mkdir /app WORKDIR /app ADD consignment-service /app/consignment-service CMD ["./consignment-service"]
Dann erstellen wir ein neues Verzeichnis, um unsere Anwendung zu hosten. Dann fügen wir unsere kompilierte Binärdatei zu unserem Docker-Container hinzu und führen sie aus.
Aktualisieren wir nun den Build-Datensatz unseres Makefiles, um ein Docker-Image zu erstellen.
build: ... GOOS=linux GOARCH=amd64 go build docker build -t consignment .
Wir haben zwei weitere Schritte hinzugefügt, und ich möchte sie näher erläutern. Zunächst erstellen wir unsere Go-Binärdatei. Sie werden jedoch zwei Umgebungsvariablen bemerken, bevor wir $ go build ausführen. Mit GOOS und GOARCH können Sie Ihre Binärdatei für ein anderes Betriebssystem überkompilieren. Da ich für ein Macbook entwickle, kann ich die ausführbare Datei go nicht kompilieren und dann in einem Docker-Container ausführen, der Linux verwendet. Die Binärdatei ist in Ihrem Docker-Container völlig bedeutungslos und es wird ein Fehler ausgegeben.
Der zweite Schritt, den ich hinzugefügt habe, ist der Docker-Erstellungsprozess. Docker liest Ihre Docker-Datei und erstellt ein Bild mit dem Namen "Konsignationsdienst". Der Punkt gibt den Verzeichnispfad an. Hier soll der Erstellungsprozess nur das aktuelle Verzeichnis anzeigen.
Ich werde unserem Makefile einen neuen Eintrag hinzufügen:
run: docker run -p 50051:50051 shippy-service-consignment
Hier starten wir unser Docker-Image durch Öffnen von Port 50051. Da Docker auf einer separaten Netzwerkebene ausgeführt wird, müssen Sie den Port umleiten. Wenn Sie diesen Dienst beispielsweise an Port 8080 starten möchten, müssen Sie das Argument -p in 8080: 50051 ändern. Sie können den Container auch im Hintergrund ausführen, indem Sie das Flag -d einfügen. Zum Beispiel führt
Docker -d -p 50051: 50051 aus .
Führen Sie
$ make run aus , und führen Sie dann in einem separaten Terminalfenster erneut
go go main.go aus und überprüfen Sie, ob es noch funktioniert.
Wenn Sie $ docker build ausführen, binden Sie Ihren Code und Ihre Laufzeit in das Image ein. Docker-Images sind portable Images Ihrer Umgebung und ihrer Abhängigkeiten. Sie können Docker-Bilder freigeben, indem Sie sie im Docker Hub veröffentlichen. Dies ähnelt npm oder dem yum-Repository für Docker-Images. Wenn Sie FROM in Ihrer Docker-Datei definieren, weisen Sie Docker an, dieses Image aus dem Docker-Repository zur Verwendung als Basis abzurufen. Sie können dann Teile dieser Basisdatei erweitern und neu definieren und sie nach Ihren Wünschen neu definieren. Wir werden unsere Docker-Images nicht veröffentlichen, können jedoch das Docker-Repository durchsuchen und feststellen, dass fast jede Software bereits in Containern verpackt wurde. Einige wirklich wundervolle Dinge wurden angedockt.
Jede Anzeige in Dockerfile wird beim ersten Erstellen zwischengespeichert. Dadurch entfällt die Notwendigkeit, die gesamte Laufzeit bei jeder Änderung neu zu erstellen. Der Docker ist intelligent genug, um herauszufinden, welche Details sich geändert haben und welche neu erstellt werden müssen. Dies macht den Erstellungsprozess unglaublich schnell.
Genug von den Containern! Kehren wir zu unserem Code zurück.
Beim Erstellen des gRPC-Dienstes gibt es viele Standardcodes zum Erstellen von Verbindungen, und Sie müssen den Speicherort der Dienstadresse im Client oder einem anderen Dienst fest codieren, damit er eine Verbindung zu ihm herstellen kann. Dies ist schwierig, da beim Starten von Diensten in der Cloud möglicherweise nicht derselbe Host verwendet wird oder sich die Adresse oder IP-Adresse nach der erneuten Bereitstellung des Dienstes ändert.
Hier kommt der Discovery-Service ins Spiel. Der Erkennungsdienst aktualisiert das Verzeichnis aller Ihrer Dienste und deren Speicherorte. Jeder Dienst wird zur Laufzeit registriert und beim Schließen abgemeldet. Jedem Dienst wird dann ein Name oder eine Kennung zugewiesen. Selbst wenn es eine neue IP-Adresse oder eine neue Hostadresse hat, müssen Sie daher keine Anrufe von anderen Diensten auf diesen Dienst aktualisieren, sofern der Dienstname gleich bleibt.
In der Regel gibt es viele Ansätze für dieses Problem, aber wie die meisten Dinge in der Programmierung macht es keinen Sinn, das Rad neu zu erfinden, wenn sich jemand bereits mit diesem Problem befasst hat. @Chuhnk (Asim Aslam), der Erfinder von
Go-micro , löst diese Probleme mit fantastischer Klarheit und Benutzerfreundlichkeit. Er produziert im Alleingang fantastische Software. Bitte helfen Sie ihm, wenn Ihnen das gefällt, was Sie sehen!
Gehen Sie Mikro
Go-micro ist ein leistungsstarkes Microservice-Framework, das in Go geschrieben wurde und größtenteils mit Go verwendet werden kann. Sie können Sidecar jedoch verwenden, um mit anderen Sprachen zu interagieren.
Go-micro bietet nützliche Funktionen zum Erstellen von Microservices in Go. Aber wir werden mit dem vielleicht häufigsten Problem beginnen, das er löst, und dies ist die Entdeckung eines Dienstes.
Wir müssen einige Aktualisierungen an unserem Service vornehmen, um mit go-micro arbeiten zu können. Go-micro wird als Protoc-Plugin integriert und ersetzt in diesem Fall das derzeit verwendete Standard-gRPC-Plugin. Beginnen wir also damit, dies in unserem Makefile zu ersetzen.
Stellen Sie sicher, dass Sie die go-micro-Abhängigkeiten installieren:
go get -u github.com/micro/protobuf/{proto,protoc-gen-go}
Aktualisieren Sie unser Makefile, um das Go-Micro-Plugin anstelle des gRPC-Plugins zu verwenden:
build: protoc -I. --go_out=plugins=micro:. \ proto/consignment/consignment.proto GOOS=linux GOARCH=amd64 go build docker build -t consignment . run: docker run -p 50051:50051 shippy-service-consignment
Jetzt müssen wir unsere Shippy-Service-Sendung / main.go aktualisieren, um go-micro verwenden zu können. Dies abstrahiert den größten Teil unseres vorherigen gRPC-Codes. Es verarbeitet problemlos die Registrierung und beschleunigt das Schreiben eines Dienstes.
Shippy-Service-Sendung / main.go Die wichtigste Änderung hierbei ist die Art und Weise, wie wir unseren gRPC-Server erstellen, der sauber von mico.NewService () abstrahiert wurde, der die Registrierung unseres Dienstes übernimmt. Und schließlich die Funktion service.Run (), die die Verbindung selbst verarbeitet. Nach wie vor registrieren wir unsere Implementierung, diesmal jedoch mit einer etwas anderen Methode.
Die zweitgrößte Änderung betrifft die Dienstmethoden selbst: Die Argumente und Antworttypen werden geringfügig geändert, um sowohl die Anforderungs- als auch die Antwortstruktur als Argumente zu akzeptieren, und geben jetzt nur noch einen Fehler zurück. In unseren Methoden legen wir die Antwort fest, die Go-Micro-Prozesse verarbeiten.
Schließlich programmieren wir den Port nicht mehr. Go-micro muss mithilfe von Umgebungsvariablen oder Befehlszeilenargumenten konfiguriert werden. Verwenden Sie zum Festlegen der Adresse MICRO_SERVER_ADDRESS =: 50051. Standardmäßig verwendet Micro MDNS (Multicast-DNS) als Service Discovery Broker für die lokale Verwendung. Normalerweise verwenden Sie mdns nicht, um Services in einer Produktionsumgebung zu ermitteln. Wir möchten jedoch vermeiden, dass Sie zum Testen etwas wie Consul oder etcd lokal ausführen müssen. Dazu später mehr.
Lassen Sie uns unser Makefile aktualisieren, um dies widerzuspiegeln.
build: protoc -I. --go_out=plugins=micro:. \ proto/consignment/consignment.proto GOOS=linux GOARCH=amd64 go build docker build -t consignment . run: docker run -p 50051:50051 \ -e MICRO_SERVER_ADDRESS=:50051 \ shippy-service-consignment
-e ist das Flag der Umgebungsvariablen. Sie können Umgebungsvariablen an Ihren Docker-Container übergeben. Sie müssen für jede Variable ein Flag haben, z. B. -e ENV = Staging -e DB_HOST = localhost usw.
Wenn Sie jetzt $ make run ausführen, verfügen Sie über einen Dockerised-Dienst mit Diensterkennung. Aktualisieren wir also unser Cli-Tool, um dies zu verwenden.
Sendung-Cli package main import ( "encoding/json" "io/ioutil" "log" "os" "context" pb "github.com/EwanValentine/shippy-service-consignment/proto/consignment" micro "github.com/micro/go-micro" ) const ( address = "localhost:50051" defaultFilename = "consignment.json" ) func parseFile(file string) (*pb.Consignment, error) { var consignment *pb.Consignment data, err := ioutil.ReadFile(file) if err != nil { return nil, err } json.Unmarshal(data, &consignment) return consignment, err } func main() { service := micro.NewService(micro.Name("shippy.cli.consignment")) service.Init() client := pb.NewShippingServiceClient("shippy.service.consignment", service.Client())
Hier haben wir die Go-Micro-Bibliotheken zum Erstellen von Clients importiert und den vorhandenen Verbindungscode durch den Go-Micro-Client-Code ersetzt, der die Berechtigung des Dienstes verwendet, anstatt eine direkte Verbindung zur Adresse herzustellen.
Wenn Sie dies jedoch ausführen, funktioniert es nicht. Dies liegt daran, dass wir unseren Dienst jetzt im Docker-Container starten, der über eigene MDNS verfügt, die von dem derzeit verwendeten MDNS-Host getrennt sind. Der einfachste Weg, dies zu beheben, besteht darin, sicherzustellen, dass sowohl der Dienst als auch der Client im Dockerland ausgeführt werden, sodass beide auf demselben Host arbeiten und dieselbe Netzwerkschicht verwenden. Erstellen wir also make consignment-cli / Makefile und erstellen einige Einträge.
build: GOOS=linux GOARCH=amd64 go build docker build -t shippy-cli-consignment . run: docker run shippy-cli-consignment
Nach wie vor wollen wir unsere Binärdatei für Linux erstellen. Wenn wir unser Docker-Image starten, möchten wir eine Umgebungsvariable übergeben, um dem Befehl go-micro die Verwendung von mdns zu erteilen.
Jetzt erstellen wir eine Docker-Datei für unser CLI-Tool:
FROM alpine:latest RUN mkdir -p /app WORKDIR /app ADD consignment.json /app/consignment.json ADD consignment-cli /app/consignment-cli CMD ["./shippy-cli-consignment"]
Dies ist unserem Service Dockerfile sehr ähnlich, außer dass es auch unsere JSON-Datendatei extrahiert.
Wenn Sie jetzt $ make run in Ihrer Shippy-Cli-Sendung ausführen, sollten Sie Created: true wie zuvor sehen.
Jetzt scheint es an der Zeit, einen Blick auf die neue Docker-Funktion zu werfen: mehrstufige Builds. Dadurch können wir mehrere Docker-Images in einer Docker-Datei verwenden.
Dies ist in unserem Fall besonders nützlich, da wir ein Bild verwenden können, um unsere Binärdatei mit allen korrekten Abhängigkeiten zu erstellen. Verwenden Sie dann das zweite Image, um es zu starten. Versuchen wir dies, ich werde detaillierte Kommentare zusammen mit dem Code hinterlassen:
Sendungsservice / Dockerfile # consignment-service/Dockerfile # golang, # . `as builder`, # , . FROM golang:alpine as builder RUN apk --no-cache add git # gopath WORKDIR /app/shippy-service-consignment # COPY . . RUN go mod download # , # Alpine. RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shippy-service-consignment # FROM, # Docker . FROM alpine:latest # , - RUN apk --no-cache add ca-certificates # , . RUN mkdir /app WORKDIR /app # , , # `builder` # , # , , # . ! COPY --from=builder /app/shippy-service-consignment/shippy-service-consignment . # ! # # run time . CMD ["./shippy-service-consignment"]
Jetzt werde ich zu anderen Docker-Dateien übergehen und diesen neuen Ansatz wählen. Oh, und vergiss nicht, $ go build von deinen Makefiles zu entfernen!
Schiffsservice
Lassen Sie uns einen zweiten Dienst erstellen. Wir haben einen Service (Shippy-Service-Sendung), der sich mit der Koordination der Containercharge mit dem Schiff befasst, das für diese Charge am besten geeignet ist. Um unserer Charge zu entsprechen, müssen wir das Gewicht und die Anzahl der Container an unseren neuen Schiffsservice senden, der dann ein Schiff findet, das diese Charge verarbeiten kann.
Erstellen Sie ein neues Verzeichnis in Ihrem
$ mkdir-Schiffsdienst- Stammverzeichnis. Erstellen Sie jetzt ein Unterverzeichnis für unsere neue Protobuf-Dienstdefinition,
$ mkdir -p shippy-service-Schiff / Proto / Schiff . Jetzt erstellen wir eine neue Protobuf-Datei,
$ touch shippy-service-ship / proto / ship / ship.proto .
Da die Definition von Protobuf in der Tat der Kern unseres Software-Designs ist, beginnen wir damit.
Schiff / Schiff.proto // shippy-service-vessel/proto/vessel/vessel.proto syntax = "proto3"; package vessel; service VesselService { rpc FindAvailable(Specification) returns (Response) {} } message Vessel { string id = 1; int32 capacity = 2; int32 max_weight = 3; string name = 4; bool available = 5; string owner_id = 6; } message Specification { int32 capacity = 1; int32 max_weight = 2; } message Response { Vessel vessel = 1; repeated Vessel vessels = 2; }
Wie Sie sehen können, ist dies unserem ersten Service sehr ähnlich. Wir erstellen einen Service mit einer RPC-Methode namens FindAvailable. Dies nimmt eine Art von Spezifikation an und gibt eine Art von Antwort zurück. Der Antworttyp gibt entweder den Schiffstyp oder mehrere Schiffe über ein sich wiederholendes Feld zurück.
Jetzt müssen wir ein Makefile erstellen, um unsere Build-Logik und unser Startskript zu verarbeiten.
$ touch Shippy-Service-Schiff / Makefile . Öffnen Sie diese Datei und fügen Sie Folgendes hinzu:
// vessel-service/Makefile build: protoc -I. --go_out=plugins=micro:. \ proto/vessel/vessel.proto docker build -t shippy-service-vessel . run: docker run -p 50052:50051 -e MICRO_SERVER_ADDRESS=:50051 shippy-service-vessel
Dies ist fast identisch mit dem ersten Makefile, das wir für unseren Sendungsservice erstellt haben. Beachten Sie jedoch, dass sich die Namen der Services und Ports etwas geändert haben. Wir können nicht zwei Dock-Container am selben Port starten, daher verwenden wir die Docker-Portweiterleitung, damit dieser Dienst im Host-Netzwerk von 50051 auf 50052 umleitet.
Jetzt brauchen wir eine Docker-Datei mit unserem neuen mehrstufigen Format:
# vessel-service/Dockerfile FROM golang:alpine as builder RUN apk --no-cache add git WORKDIR /app/shippy-service-vessel COPY . . RUN go mod download RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shippy-service-vessel FROM alpine:latest RUN apk --no-cache add ca-certificates RUN mkdir /app WORKDIR /app COPY --from=builder /app/shippy-service-vessel . CMD ["./shippy-service-vessel"]
Schließlich können wir unsere Implementierung schreiben:
Kommen wir nun zum interessanten Teil. Wenn wir eine Sendung erstellen, müssen wir unseren Frachtumschlagdienst ändern, um den Schiffssuchdienst zu kontaktieren, das Schiff zu finden und den Parameter ship_id in der erstellten Sendung zu aktualisieren:
Versand / Sendungsservice / main.go package main import ( "context" "fmt" "log" "sync" pb "github.com/EwanValentine/shippy-service-consignment/proto/consignment" vesselProto "github.com/EwanValentine/shippy-service-vessel/proto/vessel" "github.com/micro/go-micro" ) const ( port = ":50051" ) type repository interface { Create(*pb.Consignment) (*pb.Consignment, error) GetAll() []*pb.Consignment }
Hier haben wir eine Client-Instanz für unseren Schiffsservice erstellt, mit der wir den Servicenamen verwenden können, d. H. shipy.service.vessel, um den Schiffsservice als Client aufzurufen und mit seinen Methoden zu interagieren. ( FindAvailable ). , , . .
consignment-cli / consignment.json, ship_id, , . . Zum Beispiel:
{ "description": " ", "weight": 55000, "containers": [ { "customer_id": "_001", "user_id": "_001", "origin": "--" }, { "customer_id": "_002", "user_id": "_001", "origin": "" }, { "customer_id": "_003", "user_id": "_001", "origin": "" } ] }
$ make build && make run consignment-cli. . , vessel_id.
, !
MongoDB. docker-compose .
EwanValentine