Il est temps de s'attaquer aux conteneurs
Tout d'abord, nous utilisons la dernière image Linux Alpine. Linux Alpine est une distribution Linux légère conçue et optimisée pour exécuter des applications Web dans Docker. En d'autres termes, Linux Alpine possède suffisamment de dépendances et de fonctionnalités pour exécuter la plupart des applications. Cela signifie que la taille de l'image est d'environ 8 Mo!
Par rapport à, disons ... une machine virtuelle Ubuntu d'une capacité d'environ 1 Go, c'est pourquoi les images Docker sont devenues plus naturelles pour les microservices et le cloud computing.
Donc, maintenant j'espère que vous voyez de la valeur dans la conteneurisation, et nous pouvons commencer à «Dockeriser» notre premier service. Créons un Dockerfile
$ touch consignment
-service / Dockerfile .
Première partieDépôt d'origine EwanValentineArticle originalDans le Dockerfile, ajoutez les éléments suivants:
FROM alpine:latest RUN mkdir /app WORKDIR /app ADD consignment-service /app/consignment-service CMD ["./consignment-service"]
Ensuite, nous créons un nouveau répertoire pour héberger notre application. Ensuite, nous ajoutons notre binaire compilé à notre conteneur Docker et l'exécutons.
Maintenant, mettons à jour l'enregistrement de construction de notre Makefile pour créer une image Docker.
build: ... GOOS=linux GOARCH=amd64 go build docker build -t consignment .
Nous avons ajouté deux autres étapes et je voudrais les expliquer plus en détail. Tout d'abord, nous créons notre binaire Go. Cependant, vous remarquerez deux variables d'environnement avant d'exécuter $ go build. GOOS et GOARCH vous permettent de compiler de façon croisée votre binaire pour un autre système d'exploitation. Étant donné que je développe pour un Macbook, je ne peux pas compiler l'exécutable go, puis l'exécuter dans un conteneur Docker qui utilise Linux. Le binaire n'aura aucun sens dans votre conteneur Docker et générera une erreur.
La deuxième étape que j'ai ajoutée est le processus de construction du docker. Docker lira votre Dockerfile et créera une image nommée consignment-service, le point indique le chemin du répertoire, donc ici, nous voulons juste que le processus de construction regarde le répertoire actuel.
Je vais ajouter une nouvelle entrée à notre Makefile:
run: docker run -p 50051:50051 shippy-service-consignment
Ici, nous lançons notre image Docker en ouvrant le port 50051. Puisque Docker fonctionne sur une couche réseau distincte, vous devez rediriger le port. Par exemple, si vous souhaitez démarrer ce service sur le port 8080, vous devez remplacer l'argument -p par 8080: 50051. Vous pouvez également exécuter le conteneur en arrière-plan en incluant l'indicateur -d. Par exemple,
docker run -d -p 50051: 50051 consign-service .
Exécutez
$ make run , puis à nouveau dans un panneau de terminal séparé
$ go run main.go et vérifiez qu'il fonctionne toujours.
Lorsque vous exécutez la génération $ docker, vous intégrez votre code et votre runtime dans l'image. Les images Docker sont des images portables de votre environnement et de ses dépendances. Vous pouvez partager des images Docker en les publiant sur le Docker Hub. Qui est similaire à npm ou au référentiel yum pour les images docker. Lorsque vous définissez FROM dans votre Dockerfile, vous dites à Docker de retirer cette image du référentiel Docker pour l'utiliser comme base. Vous pouvez ensuite développer et redéfinir des parties de ce fichier de base, en les redéfinissant à votre guise. Nous ne publierons pas nos images de docker, mais n'hésitez pas à parcourir le référentiel de docker et à noter que presque tous les logiciels ont déjà été emballés dans des conteneurs. Certaines choses vraiment merveilleuses ont été dockées.
Chaque annonce dans Dockerfile est mise en cache la première fois qu'elle est créée. Cela élimine le besoin de reconstruire l'intégralité de l'exécution à chaque fois que vous apportez des modifications. Le docker est suffisamment intelligent pour déterminer quels détails ont changé et lesquels doivent être reconstruits. Cela rend le processus de construction incroyablement rapide.
Assez parlé des conteneurs! Revenons à notre code.
Lors de la création du service gRPC, il y a beaucoup de code standard pour créer des connexions, et vous devez coder en dur l'emplacement de l'adresse de service dans le client ou un autre service afin qu'il puisse s'y connecter. Cela est difficile car lorsque vous démarrez des services dans le cloud, ils peuvent ne pas utiliser le même hôte, ou l'adresse ou l'adresse IP peuvent changer après le redéploiement du service.
C'est là que le service de découverte entre en jeu. Le service de découverte met à jour l'annuaire de tous vos services et leurs emplacements. Chaque service est enregistré à l'exécution et se désenregistre à la fermeture. Chaque service se voit alors attribuer un nom ou un identifiant. Ainsi, même s'il peut avoir une nouvelle adresse IP ou une adresse d'hôte, à condition que le nom du service reste le même, vous n'avez pas besoin de mettre à jour les appels à ce service à partir d'autres services.
En règle générale, il existe de nombreuses approches à ce problème, mais, comme la plupart des choses en programmation, si quelqu'un a déjà traité ce problème, cela n'a aucun sens de réinventer la roue. @Chuhnk (Asim Aslam), le créateur de
Go-micro , résout ces problèmes avec une clarté et une facilité d'utilisation fantastiques. Il produit à lui seul des logiciels fantastiques. Pensez à l'aider si vous aimez ce que vous voyez!
Allez micro
Go-micro est un puissant framework de microservices écrit en Go, à utiliser, pour la plupart, avec Go. Cependant, vous pouvez utiliser Sidecar pour interagir avec d'autres langues.
Go-micro possède des fonctionnalités utiles pour créer des microservices dans Go. Mais nous allons commencer par peut-être le problème le plus courant qu'il résout, et c'est la découverte d'un service.
Nous devrons effectuer plusieurs mises à jour de notre service afin de travailler avec go-micro. Go-micro s'intègre en tant que plugin Protocoles, dans ce cas remplaçant le plugin gRPC standard que nous utilisons actuellement. Commençons donc par le remplacer dans notre Makefile.
Assurez-vous d'installer les dépendances go-micro:
go get -u github.com/micro/protobuf/{proto,protoc-gen-go}
Mettez à jour notre Makefile pour utiliser le plugin go-micro au lieu du plugin gRPC:
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
Maintenant, nous devons mettre à jour notre shippy-service-consignment / main.go pour utiliser go-micro. Ceci résume la plupart de notre code gRPC précédent. Il traite facilement l'enregistrement et accélère la rédaction d'un service.
shippy-service-consignment / main.go Le principal changement ici est la façon dont nous créons notre serveur gRPC, qui a été soigneusement abstrait de mico.NewService (), qui gère l'enregistrement de notre service. Et enfin, la fonction service.Run (), qui traite la connexion elle-même. Comme précédemment, nous enregistrons notre implémentation, mais cette fois avec une méthode légèrement différente.
Le deuxième changement le plus important concerne les méthodes de service elles-mêmes: les arguments et les types de réponses sont légèrement modifiés pour accepter à la fois la demande et les structures de réponse comme arguments, et ne renvoient désormais qu'une erreur. Dans nos méthodes, nous définissons la réponse que les processus go-micro.
Enfin, nous ne programmons plus le port. Go-micro doit être configuré à l'aide de variables d'environnement ou d'arguments de ligne de commande. Pour définir l'adresse, utilisez MICRO_SERVER_ADDRESS =: 50051. Par défaut, Micro utilise mdns (multicast dns) comme courtier de découverte de service pour une utilisation locale. Habituellement, vous n'utilisez pas mdns pour découvrir des services dans un environnement de production, mais nous voulons éviter d'avoir à exécuter localement quelque chose comme Consul ou etcd pour les tests. Plus d'informations à ce sujet plus tard.
Mettons à jour notre Makefile pour refléter cela.
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 est le drapeau de la variable d'environnement, il vous permet de passer des variables d'environnement à votre conteneur Docker. Vous devez avoir un indicateur pour chaque variable, par exemple -e ENV = staging -e DB_HOST = localhost, etc.
Maintenant, si vous exécutez $ make run, vous aurez un service Dockerised avec découverte de service. Donc, mettons à jour notre outil Cli pour l'utiliser.
consignation-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())
Ici, nous avons importé les bibliothèques go-micro pour créer des clients et remplacé le code de connexion existant par le code client go-micro, qui utilise l'autorisation du service au lieu de se connecter directement à l'adresse.
Cependant, si vous l'exécutez, cela ne fonctionnera pas. C'est parce que nous lançons maintenant notre service dans le conteneur Docker, qui a ses propres mdns, distinct de l'hôte mdns que nous utilisons actuellement. Le moyen le plus simple de résoudre ce problème est de vous assurer que le service et le client s'exécutent dans le Dockland, afin qu'ils fonctionnent tous les deux sur le même hôte et utilisent la même couche réseau. Créons donc make consignment-cli / Makefile et créons des entrées.
build: GOOS=linux GOARCH=amd64 go build docker build -t shippy-cli-consignment . run: docker run shippy-cli-consignment
Comme précédemment, nous voulons construire notre binaire pour Linux. Lorsque nous lançons notre image docker, nous voulons passer une variable d'environnement pour donner la commande go-micro pour utiliser mdns.
Créons maintenant un Dockerfile pour notre outil CLI:
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"]
Ceci est très similaire à notre service Dockerfile, sauf qu'il extrait également notre fichier de données json.
Maintenant, lorsque vous exécutez $ make run dans votre envoi shippy-cli, vous devriez voir Créé: vrai, comme auparavant.
Maintenant, il semble temps de jeter un œil à la nouvelle fonctionnalité Docker: les builds multi-étapes. Cela nous permet d'utiliser plusieurs images Docker dans un seul Dockerfile.
Ceci est particulièrement utile dans notre cas, car nous pouvons utiliser une image pour créer notre fichier binaire avec toutes les dépendances correctes. Et puis utilisez la deuxième image pour la lancer. Essayons cela, je vais laisser des commentaires détaillés avec le code:
consignation-service / 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"]
Je vais maintenant passer à d'autres fichiers Docker et adopter cette nouvelle approche. Oh, et n'oubliez pas de supprimer $ go build de vos Makefiles!
Service de bateau
Créons un deuxième service. Nous avons un service (shippy-service-consignment), qui s'occupe de la coordination du lot de conteneurs avec le navire, qui est le mieux adapté pour ce lot. Pour correspondre à notre lot, nous devons envoyer le poids et le nombre de conteneurs à notre nouveau service d'expédition, qui trouvera alors un navire capable de gérer ce lot.
Créez un nouveau répertoire dans votre répertoire racine
$ mkdir navire-service , créez maintenant un sous-répertoire pour notre nouvelle définition de services protobuf,
$ mkdir -p shippy-service-navire / proto / navire . Créons maintenant un nouveau fichier protobuf,
$ touch shippy-service-vessel / proto / vessel / vessel.proto .
Étant donné que la définition de protobuf est en effet au cœur de notre conception logicielle, commençons par elle.
navire / navire.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; }
Comme vous pouvez le voir, cela ressemble beaucoup à notre premier service. Nous créons un service avec une méthode rpc appelée FindAvailable. Cela prend un type de spécification et renvoie un type de réponse. Le type de réponse renvoie le type de navire ou plusieurs navires à l'aide d'un champ répétitif.
Nous devons maintenant créer un Makefile pour gérer notre logique de construction et notre script de démarrage.
$ touch shippy-service-navire / Makefile . Ouvrez ce fichier et ajoutez ce qui suit:
// 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
Ceci est presque identique au premier Makefile que nous avons créé pour notre service de consignation, mais notez que les noms des services et des ports ont un peu changé. Nous ne pouvons pas lancer deux conteneurs Dock sur le même port, nous utilisons donc la redirection de port Dockers pour que ce service redirige de 50051 vers 50052 sur le réseau hôte.
Nous avons maintenant besoin d'un Dockerfile utilisant notre nouveau format multi-étapes:
# 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"]
Enfin, nous pouvons écrire notre implémentation:
Passons maintenant à la partie intéressante. Lorsque nous créons un envoi, nous devons changer notre service de manutention de fret pour contacter le service de recherche de navires, trouver le navire et mettre à jour le paramètre ship_id dans l'envoi créé:
shippy / consign-service / 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 }
Ici, nous avons créé une instance client pour notre service d'expédition, qui nous permet d'utiliser le nom du service, c'est-à-dire shipy.service.vessel pour appeler le service du navire en tant que client et interagir avec ses méthodes.
Dans ce cas, une seule méthode (FindAvailable). Nous expédions le poids du lot avec le nombre de conteneurs que nous voulons expédier, comme spécification pour le service du navire. Ce qui nous renvoie le navire correspondant à cette spécification.Mettez à jour le fichier consignment-cli / consignment.json, supprimez le ship_id codé en dur, car nous voulons confirmer que notre service de recherche de navires fonctionne. Ajoutons également quelques conteneurs supplémentaires et augmentons le poids. Par exemple:
{ "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": "" } ] }
Maintenant, lancez $ make build && make run dans consignment-cli. Vous devriez voir une réponse avec une liste des biens créés. Dans vos parties, vous devriez voir que le paramètre navire_id est défini.Nous avons donc deux microservices interconnectés et une interface de ligne de commande!Dans la prochaine partie de cette série, nous envisagerons d'enregistrer certaines de ces données à l'aide de MongoDB. Nous ajouterons également un troisième service et utiliserons docker-compose pour gérer localement notre écosystème de conteneurs en pleine croissance.Partie IRéférentiel d'origine EwanValentine