6

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

 1 year ago
source link: https://www.cnblogs.com/yangjianyong-bky/p/17276194.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

基于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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK