Comment créer un microservice simple sur Golang et gRPC et le conteneuriser à l'aide de Docker

Bonjour, Habr! Je vous présente la traduction de l'article «Go, gRPC et Docker» de Mat Evans.

Il existe de nombreux articles sur le partage de Go et Docker. La création de conteneurs pouvant interagir avec les clients et entre eux est très simple. Ce qui suit est un petit exemple de la façon dont cela est fait au niveau de base.

Que créons-nous?


Nous allons créer un client et un serveur très simples, interagissant les uns avec les autres à l'aide de gRPC . Le serveur sera situé à l'intérieur du conteneur Docker afin de pouvoir être facilement déployé.

Supposons que nous ayons besoin d'un service qui reçoit une chaîne d'un client et renvoie une chaîne avec un ordre de caractères inversé. Par exemple, envoyez un «chat» et obtenez un «courant» en réponse.

fichier .proto


Le fichier .proto décrit les opérations que notre service effectuera et les données qu'il échangera. Créez le dossier proto dans le projet et le fichier reverse.proto dedans

syntax = "proto3"; package reverse; service Reverse { rpc Do(Request) returns (Response) {} } message Request { string message = 1; } message Response { string message = 1; } 

Une fonction qui est appelée à distance sur le serveur et renvoie des données au client est marquée comme rpc . Les structures de données utilisées pour échanger des informations entre des nœuds en interaction sont marquées comme message . Chaque champ de message doit recevoir un numéro de séquence. Dans ce cas, notre fonction reçoit des messages de type Request du client et retourne des messages de type Response .
Une fois que nous avons créé un fichier .proto , il est nécessaire d'obtenir le fichier .go de notre service. Pour ce faire, exécutez la commande de console suivante dans le dossier proto :

 $ protoc -I . reverse.proto --go_out=plugins=grpc:. 

Bien sûr, vous devez d'abord construire gRPC .
L'exécution de la commande ci-dessus créera un nouveau fichier .go contenant les méthodes de création du client, du serveur et des messages qu'ils échangent. Si nous appelons godoc , nous verrons ce qui suit:

 $ godoc . PACKAGE DOCUMENTATION package reverse import "." Package reverse is a generated protocol buffer package. It is generated from these files: reverse.proto It has these top-level messages: Request Response .... 

Client


Ce serait bien si notre client travaillait comme ceci:

 reverse "this is a test" tset a si siht 

Voici le code qui crée le client gRPC à l'aide de structures de données générées à partir du fichier .proto :

 package main import ( "context" "fmt" "os" pb "github.com/matzhouse/go-grpc/proto" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" ) func main() { opts := []grpc.DialOption{ grpc.WithInsecure(), } args := os.Args conn, err := grpc.Dial("127.0.0.1:5300", opts...) if err != nil { grpclog.Fatalf("fail to dial: %v", err) } defer conn.Close() client := pb.NewReverseClient(conn) request := &pb.Request{ Message: args[1], } response, err := client.Do(context.Background(), request) if err != nil { grpclog.Fatalf("fail to dial: %v", err) } fmt.Println(response.Message) } 


Serveur


Le serveur utilisera le même fichier .go généré. Cependant, il ne définit que l'interface du serveur, mais nous devons implémenter la logique par nous-mêmes:

 package main import ( "net" pb "github.com/matzhouse/go-grpc/proto" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" ) func main() { listener, err := net.Listen("tcp", ":5300") if err != nil { grpclog.Fatalf("failed to listen: %v", err) } opts := []grpc.ServerOption{} grpcServer := grpc.NewServer(opts...) pb.RegisterReverseServer(grpcServer, &server{}) grpcServer.Serve(listener) } type server struct{} func (s *server) Do(c context.Context, request *pb.Request) (response *pb.Response, err error) { n := 0 // reate an array of runes to safely reverse a string. rune := make([]rune, len(request.Message)) for _, r := range request.Message { rune[n] = r n++ } // Reverse using runes. rune = rune[0:n] for i := 0; i < n/2; i++ { rune[i], rune[n-1-i] = rune[n-1-i], rune[i] } output := string(rune) response = &pb.Response{ Message: output, } return response, nil } 


Docker


Je suppose que vous savez ce qu'est Docker et à quoi il sert. Voici notre Dockerfile :

 FROM golang:1.12 ADD . /go/src/github.com/matzhouse/go-grpc/server RUN go install github.com/matzhouse/go-grpc/server ENTRYPOINT ["/go/bin/server"] EXPOSE 5300 

Le code d'assemblage de l'image Docker est écrit ici. Nous allons l'analyser ligne par ligne.

 FROM golang:1.12 

Cette commande signifie que nous voulons créer une image de notre application sur la base d'une image précédemment créée, à savoir golang . Il s'agit d'une image Docker avec un environnement déjà configuré pour créer et exécuter des programmes écrits en Go .

 ADD . /go/src/github.com/matzhouse/go-grpc/server 

Cette commande copie le code source de notre application dans le conteneur GOPATH / src .

 RUN go install github.com/matzhouse/go-grpc/server 

Cette commande collecte notre application à partir des sources copiées dans le conteneur et l'installe dans le dossier du conteneur GOPATH / bin .

 ENTRYPOINT ["/go/bin/server"] 

Cette commande configure le conteneur pour fonctionner comme un programme exécutable. Nous y indiquons le chemin vers l'exécutable de l'application et, si nécessaire, les arguments de la ligne de commande.

 EXPOSE 5300 

Avec cette commande, nous indiquons au conteneur quels ports doivent être accessibles de l'extérieur.

Démarrage du serveur


Nous devons exécuter le conteneur avec notre application serveur.
Vous devez d'abord créer l'image en fonction des instructions du Dockerfile :

 $ sudo docker build -t matzhouse/grpc-server . Sending build context to Docker daemon 31.76 MB Step 1/5 : FROM golang ---> a0c61f0b0796 Step 2/5 : ADD . /go/src/github.com/matzhouse/go-grpc ---> 9508be6501c1 Removing intermediate container 94dc6e3a9a20 Step 3/5 : RUN go install github.com/matzhouse/go-grpc/server ---> Running in f3e0b993a420 ---> f7a0370b7f7d Removing intermediate container f3e0b993a420 Step 4/5 : ENTRYPOINT /go/bin/server ---> Running in 9c9619e45df4 ---> fb34dfe1c0ea Removing intermediate container 9c9619e45df4 Step 5/5 : EXPOSE 5300 ---> Running in 0403390af135 ---> 008e09b9aebd Removing intermediate container 0403390af135 Successfully built 008e09b9aebd 

Maintenant, nous pouvons voir cette image dans la liste:

 $ docker images REPOSITORY TAG IMAGE ID ... matzhouse/grpc-server latest 008e09b9aebd ... 

Super! Nous avons une image de notre application serveur, avec laquelle vous pouvez lancer son conteneur en utilisant la commande suivante:

 $ docker run -it -p 5300:5300 matzhouse/grpc-server 

Dans ce cas, le soi-disant redirection de port . Notez que pour cela, nous avons besoin à la fois de l'instruction EXPOSE et de l'argument -p .

Lancement client


La conteneurisation du client ne donnera pas de gros avantages, alors commençons comme d'habitude:

 $ go build -o reverse $ ./reverse "this is a test" tset a si siht 

Merci d'avoir lu!

Source: https://habr.com/ru/post/fr461279/


All Articles