基於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
本專案為基於 Go/Grpc/kubernetes/Istio
開發微服務的最佳實踐提供參考。
並基於 Jenkins/Gitlab/Harbor
實現了CICD
。
並使用 grpc-gateway
作為閘道器代理。
本最佳實踐分為三個部分:
pingservice
的微服務pongservice
的微服務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
在src
目錄下建立名為pongservice
的微服務,目錄結構如下:
pongservice
├── cmd
│ └── server
├── config
├── proto
│ └── v1
└── service
6 directories, 0 files
在 pongservice/proto 目錄下執行以下命令:
buf mod init
此命令建立一個名為 buf.yaml
的檔案,位於 ponservice/proto
目錄中。
buf.yaml
的程式碼如下:
version: v1
breaking:
use:
- FILE
lint:
use:
- DEFAULT
在 version
和 breaking
之間新增如下依賴程式碼:
deps:
- buf.build/googleapis/googleapis
請檢視新增此依賴程式碼的原因:https://github.com/grpc-ecosystem/grpc-gateway
新增依賴程式碼後完整的 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/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/proto/v1
目錄中使用以下程式碼建立一個名為 pongservice.yaml
的原型檔案:
type: google.api.Service
config_version: 3
http:
rules:
- selector: proto.v1.PongService.Pong
get: /pong.v1.pong
在 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
在 pongservice
目錄中建立 go.mod
:
go mod init github.com/janrs-io/Jgrpc/src/pongservice && go mod tidy
在 pongservice/config
目錄下,建立 config.yaml
檔案並新增以下程式碼:
# grpc config
grpc:
host: ""
port: ":50051"
name: "pong-grpc"
# http config
http:
host: ""
port: ":9001"
name: "pong-http"
在 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
。
在 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
}
在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
檔案。
最後一步,在 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)
}
在 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