• 基于Go/Grpc/kubernetes/Istio开发微服务的最佳实践尝试 - 1/3


    基于Go/Grpc/kubernetes/Istio开发微服务的最佳实践尝试 - 1/3

    基于Go/Grpc/kubernetes/Istio开发微服务的最佳实践尝试 - 2/3

    基于Go/Grpc/kubernetes/Istio开发微服务的最佳实践尝试 - 3/3

    项目地址:https://github.com/janrs-io/Jgrpc


    转载请注明来源:https://janrs.com/br6f


    Jgrpc

    本项目为基于 Go/Grpc/kubernetes/Istio 开发微服务的最佳实践提供参考。

    并基于 Jenkins/Gitlab/Harbor 实现了CICD

    并使用 grpc-gateway 作为网关代理。

    本最佳实践分为三个部分:

    1. 创建一个 pingservice 的微服务
    2. 创建一个 pongservice 的微服务
    3. 基于Jenkins/Gitlab/Harbor 创建 CICD 部署流程并部署到 k8s/istio

    在这一部分中,我们将创建 pongservice 微服务。


    前提

    假设已经安装了以下工具:

    • protoc-gen-grpc-gateway
    • protoc-gen-openapiv2
    • protoc-gen-go
    • protoc-gen-go-grpc
    • buf
    • wire

    下面是安装这些工具的教程地址:

    protoc-gen-grpc-gateway & protoc-gen-openapiv2 & protoc-gen-go & protoc-gen-go-grpc
    这四个工具的安装教程请查看:install protoc-gen* tools

    wire
    wire 工具的安装教程请查看:install wire tool

    buf
    buf 工具的安装教程请查看:install buf tool

    项目结构

    这部分最终的目录结构如下:

    Jgrpc
    ├── devops
    ├── istio-manifests
    ├── kubernetes-manifests
    └── src
        └── pongservice
            ├── buf.gen.yaml
            ├── cmd
            │   ├── main.go
            │   └── server
            │       ├── grpc.go
            │       ├── http.go
            │       ├── run.go
            │       ├── wire.go
            │       └── wire_gen.go
            ├── config
            │   ├── config.go
            │   └── config.yaml
            ├── genproto
            │   └── v1
            │       ├── gw
            │       │   └── pongservice.pb.gw.go
            │       ├── pongservice.pb.go
            │       └── pongservice_grpc.pb.go
            ├── go.mod
            ├── go.sum
            ├── proto
            │   ├── buf.lock
            │   ├── buf.yaml
            │   └── v1
            │       ├── pongservice.proto
            │       └── pongservice.yaml
            └── service
                ├── client.go
                └── server.go
    
    14 directories, 20 files
    

    开始

    创建项目的整体目录结构如下:

    Jgrpc
    ├── devops
    ├── istio-manifests
    ├── kubernetes-manifests
    ├── src
    

    创建 pongservice 微服务

    创建基本目录

    src目录下创建名为pongservice的微服务,目录结构如下:

    pongservice
    ├── cmd
    │   └── server
    ├── config
    ├── proto
    │   └── v1
    └── service
    
    6 directories, 0 files
    

    生成代码和文件

    生成 buf.yaml

    在 pongservice/proto 目录下执行以下命令:

    buf mod init
    

    此命令创建一个名为 buf.yaml 的文件,位于 ponservice/proto 目录中。
    buf.yaml的代码如下:

    version: v1
    breaking:
      use:
        - FILE
    lint:
      use:
        - DEFAULT
    

    versionbreaking 之间添加如下依赖代码:

    deps:
      - buf.build/googleapis/googleapis
    

    请查看添加此依赖代码的原因:https://github.com/grpc-ecosystem/grpc-gateway#3-external-configuration

    添加依赖代码后完整的 buf.yaml 文件如下:

    version: v1
    deps:
      - buf.build/googleapis/googleapis
    breaking:
      use:
        - FILE
    lint:
      use:
        - DEFAULT
    

    然后在pongservice/proto目录下执行如下命令:

    buf mod update
    

    执行命令后会生成一个buf.lock文件,代码如下:

    # Generated by buf. DO NOT EDIT.
    version: v1
    deps:
      - remote: buf.build
        owner: googleapis
        repository: googleapis
        commit: 463926e7ee924d46ad0a726e1cf4eacd
    

    生成 pongservice.proto

    pongservice/proto/v1 目录中使用以下代码创建一个名为 pongservice.proto 的原型文件:

    syntax = "proto3";
    
    package proto.v1;
    
    option go_package = "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1";
    
    service PongService {
      rpc Pong(PongRequest) returns(PongResponse){}
    }
    
    message PongRequest {
      string msg = 1 ;
    }
    
    message PongResponse {
      string msg = 1;
    }
    

    生成 pongservice.yaml

    pongservice/proto/v1 目录中使用以下代码创建一个名为 pongservice.yaml 的原型文件:

    type: google.api.Service
    config_version: 3
    
    http:
      rules:
        - selector: proto.v1.PongService.Pong
          get: /pong.v1.pong
    

    生成 buf.gen.yaml

    pongservice 目录中使用以下代码创建一个名为 buf.gen.yaml 的 yaml 文件:

    version: v1
    plugins:
      - plugin: go
        out: genproto/v1
        opt:
          - paths=source_relative
      - plugin: go-grpc
        out: genproto/v1
        opt:
          - paths=source_relative
      - plugin: grpc-gateway
        out: genproto/v1/gw
        opt:
          - paths=source_relative
          - grpc_api_configuration=proto/v1/pongservice.yaml
          - standalone=true
    

    pongservice 目录中,执行以下命令:

    buf generate proto/v1
    

    执行命令后,会在pongservice目录下自动创建一个genproto目录,该目录下有以下文件:

    genproto
    └── v1
        ├── gw
        │   └── ponservice.pb.gw.go
        ├── ponservice.pb.go
        └── ponservice_grpc.pb.go
    
    2 directories, 3 files
    

    生成 go.mod

    pongservice 目录中创建 go.mod

    go mod init github.com/janrs-io/Jgrpc/src/pongservice && go mod tidy
    

    生成 config.yaml

    pongservice/config 目录下,创建 config.yaml 文件并添加以下代码:

    # grpc config
    grpc:
      host: ""
      port: ":50051"
      name: "pong-grpc"
    
    # http config
    http:
      host: ""
      port: ":9001"
      name: "pong-http"
    

    生成 config.go

    pongservice/config 目录下,创建 config.go 文件并添加以下代码:

    package config
    
    import (
    	"net/http"
    
    	"github.com/spf13/viper"
    	"google.golang.org/grpc"
    )
    
    // Config Service config
    type Config struct {
    	Grpc Grpc `json:"grpc" yaml:"grpc"`
    	Http Http `json:"http" yaml:"http"`
    }
    
    // NewConfig Initial service's config
    func NewConfig(cfg string) *Config {
    
    	if cfg == "" {
    		panic("load config file failed.config file can not be empty.")
    	}
    
    	viper.SetConfigFile(cfg)
    
    	// Read config file
    	if err := viper.ReadInConfig(); err != nil {
    		panic("read config failed.[ERROR]=>" + err.Error())
    	}
    	conf := &Config{}
    	// Assign the overloaded configuration to the global
    	if err := viper.Unmarshal(conf); err != nil {
    		panic("assign config failed.[ERROR]=>" + err.Error())
    	}
    
    	return conf
    
    }
    
    // Grpc Grpc server config
    type Grpc struct {
    	Host   string `json:"host" yaml:"host"`
    	Port   string `json:"port" yaml:"port"`
    	Name   string `json:"name" yaml:"name"`
    	Server *grpc.Server
    }
    
    // Http Http server config
    type Http struct {
    	Host   string `json:"host" yaml:"host"`
    	Port   string `json:"port" yaml:"port"`
    	Name   string `json:"name" yaml:"name"`
    	Server *http.Server
    }
    

    然后在 pongservice 目录中再次运行 go mod tidy

    生成 client.go

    pongservice/service 目录下,创建 client.go 文件并添加以下代码:

    package service
    
    import (
    	"context"
    	"time"
    
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials/insecure"
    
    	"github.com/janrs-io/Jgrpc/src/pongservice/config"
    	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
    )
    
    // NewClient New service's client
    func NewClient(conf *config.Config) (v1.PongServiceClient, error) {
    
    	serverAddress := conf.Grpc.Host + conf.Grpc.Port
    	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    	defer cancel()
    
    	conn, err := grpc.DialContext(ctx, serverAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
    	if err != nil {
    		return nil, err
    	}
    	client := v1.NewPongServiceClient(conn)
    	return client, nil
    
    }
    

    pongservice/service 目录下,创建 server.go 文件并添加以下代码:

    package service
    
    import (
    	"context"
    
    	"github.com/janrs-io/Jgrpc/src/pongservice/config"
    	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
    )
    
    // Server Server struct
    type Server struct {
    	v1.UnimplementedPongServiceServer
    	pongClient v1.PongServiceClient
    	conf       *config.Config
    }
    
    // NewServer New service grpc server
    func NewServer(conf *config.Config, pongClient v1.PongServiceClient) v1.PongServiceServer {
    	return &Server{
    		pongClient: pongClient,
    		conf:       conf,
    	}
    }
    
    func (s *Server) Pong(ctx context.Context, req *v1.PongRequest) (*v1.PongResponse, error) {
    	return &v1.PongResponse{Msg: "response pong msg:" + req.Msg}, nil
    }
    

    生成 run server 文件

    pongservice/cmd/server目录下,创建以下四个文件:

    • grpc.go
    • http.go
    • run.go
    • wire.go

    将以下代码添加到 grpc.go 文件中:

    package server
    
    import (
    	"fmt"
    	"log"
    	"net"
    
    	"google.golang.org/grpc"
    
    	"github.com/janrs-io/Jgrpc/src/pongservice/config"
    	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
    )
    
    // RunGrpcServer Run grpc server
    func RunGrpcServer(server v1.PongServiceServer, conf *config.Config) {
    
    	grpcServer := grpc.NewServer()
    	v1.RegisterPongServiceServer(grpcServer, server)
    
    	fmt.Println("Listening grpc server on port" + conf.Grpc.Port)
    	listen, err := net.Listen("tcp", conf.Grpc.Port)
    	if err != nil {
    		panic("listen grpc tcp failed.[ERROR]=>" + err.Error())
    	}
    
    	go func() {
    		if err = grpcServer.Serve(listen); err != nil {
    			log.Fatal("grpc serve failed", err)
    		}
    	}()
    
    	conf.Grpc.Server = grpcServer
    
    }
    

    将以下代码添加到 http.go 文件中:

    package server
    
    import (
    	"context"
    	"fmt"
    	"net/http"
    
    	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    	"google.golang.org/grpc"
    	"google.golang.org/grpc/credentials/insecure"
    
    	"github.com/janrs-io/Jgrpc/src/pongservice/config"
    	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1/gw"
    )
    
    // RunHttpServer Run http server
    func RunHttpServer(conf *config.Config) {
    
    	mux := runtime.NewServeMux()
    	opts := []grpc.DialOption{
    		grpc.WithTransportCredentials(insecure.NewCredentials()),
    	}
    
    	if err := v1.RegisterPongServiceHandlerFromEndpoint(
    		context.Background(),
    		mux,
    		conf.Grpc.Port,
    		opts,
    	); err != nil {
    		panic("register service handler failed.[ERROR]=>" + err.Error())
    	}
    
    	httpServer := &http.Server{
    		Addr:    conf.Http.Port,
    		Handler: mux,
    	}
    	fmt.Println("Listening http server on port" + conf.Http.Port)
    
    	go func() {
    		if err := httpServer.ListenAndServe(); err != nil {
    			fmt.Println("listen http server failed.[ERROR]=>" + err.Error())
    		}
    	}()
    
    	conf.Http.Server = httpServer
    
    }
    

    将以下代码添加到 run.go 文件中:

    package server
    
    import (
    	"context"
    	"fmt"
    	"os"
    	"os/signal"
    	"syscall"
    	"time"
    
    	"github.com/janrs-io/Jgrpc/src/pongservice/config"
    	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
    )
    
    // Run Run service server
    func Run(cfg string) {
    
    	conf := config.NewConfig(cfg)
    	// run grpc server
    	RunGrpcServer(initServer(conf), conf)
    	// run http server
    	RunHttpServer(conf)
    	// listen exit server event
    	HandleExitServer(conf)
    
    }
    
    // SetServer Wire inject service's component
    func initServer(conf *config.Config) v1.PongServiceServer {
    	server, err := InitServer(conf)
    	if err != nil {
    		panic("run server failed.[ERROR]=>" + err.Error())
    	}
    	return server
    }
    
    // HandleExitServer Handle service exit event
    func HandleExitServer(conf *config.Config) {
    
    	ch := make(chan os.Signal, 1)
    	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
    	<-ch
    	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    	defer cancel()
    	conf.Grpc.Server.GracefulStop()
    	if err := conf.Http.Server.Shutdown(ctx); err != nil {
    		panic("shutdown service failed.[ERROR]=>" + err.Error())
    	}
    	<-ctx.Done()
    	close(ch)
    	fmt.Println("Graceful shutdown http & grpc server.")
    
    }
    

    将以下代码添加到 wire.go 文件中:

    //go:build wireinject
    // +build wireinject
    
    package server
    
    import (
    	"github.com/google/wire"
    
    	"github.com/janrs-io/Jgrpc/src/pongservice/config"
    	v1 "github.com/janrs-io/Jgrpc/src/pongservice/genproto/v1"
    	"github.com/janrs-io/Jgrpc/src/pongservice/service"
    )
    
    // InitServer Inject service's component
    func InitServer(conf *config.Config) (v1.PongServiceServer, error) {
    
    	wire.Build(
    		service.NewClient,
    		service.NewServer,
    	)
    
    	return &service.Server{}, nil
    
    }
    

    pongservice 目录中再次运行 go mod tidy

    go mod tidy
    

    然后在 pongservice 目录中执行以下 wire 命令:

    wire ./...
    

    执行 wire 命令后,将在 pongsevice/cmd/server 目录中自动创建 wire_gen.go 文件。

    生成 main.go

    最后一步,在 pongservice/cmd 目录下创建 main.go 文件.

    package main
    
    import (
    	"flag"
    
    	"github.com/janrs-io/Jgrpc/src/pongservice/cmd/server"
    )
    
    var cfg = flag.String("config", "config/config.yaml", "config file location")
    
    // main main
    func main() {
    	server.Run(*cfg)
    }
    

    启动 service

    pongservice 目录下执行以下命令启动微服务:

    注意
    pongservice 目录而不是 pongservice/cmd 目录中执行命令

    go run cmd/main.go
    

    启动服务后,会显示如下信息:

    Listening grpc server on port:50051
    Listening http server on port:9001
    

    在浏览器中输入以下地址即可访问该服务:

    127.0.01:9001/pong.v1.pong?msg=best practice
    

    如果成功,将返回以下数据:

    {
        "msg": "response pong msg:best practice"
    }
    

    总结

    现在,我们已经了解了如何创建可以开发基本功能微服务的项目结构。

    在接下来的部分中,我们继续创建一个名为 pingservice 的微服务,并访问我们在这部分中创建的 pongservice


    转载请注明来源:https://janrs.com/br6f

  • 相关阅读:
    Python爬虫与数据可视化源码免费领取
    最佳实践:REST API 的 HTTP 请求参数
    Spring底层的核心原理解析
    实现简单的weak_ptr
    JS——手风琴|鼠标悬停图片滚动展示栏 [技术栈:html、css、JavaScript]
    英伟达jetson硬件(NX,nano,AGX,TX1,TX2)通用开机自动开启风扇教程
    css h5 端弹窗时禁止底部页面滚动
    【Leetcode Sheet】Weekly Practice 7
    2022杭电多校赛第八场
    postgresql 格式化查询树为图片 —— pgNodeGraph 与 pg_node2graph
  • 原文地址:https://www.cnblogs.com/yangjianyong-bky/p/17276194.html