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

news2024/12/23 18:08:03

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

在 version 和 breaking 之间添加如下依赖代码:

deps:
  - buf.build/googleapis/googleapis

请查看添加此依赖代码的原因:GitHub - grpc-ecosystem/grpc-gateway: gRPC to JSON proxy generator following the gRPC HTTP spec

添加依赖代码后完整的 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/777580.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

万宾管网水质监测仪 | 守护城市水生态环境

WITBEE万宾 管网水质监测仪EN400-WQ&#xff0c;新一代城市生命线智能监测仪器&#xff0c;可选 COD、氨氮和电导率等7项组合监测指标&#xff0c;4G无线通信适应地下排水管网极端恶劣环境&#xff0c;3年自供电长续航&#xff01;

【远程访问】文件同步工具:本地搭建免费开源的Syncthing自动文件同步服务器

文章目录 1.前言2. Syncthing网站搭建2.1. Syncthing下载和安装2.2. Syncthing网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 在数据爆炸的当下&#xff0c;每天都会产生海量的数据&#xff0c;这些数据可…

手劈二叉树

二叉树 概念 二叉树是一种常见的树状数据结构&#xff0c;每个节点最多有两个子节点&#xff0c;称为左子节点 和右子节点。它可以为空树&#xff08;没有任何节点&#xff09;&#xff0c;或者由根节点及其子节点组成。特点 具有层级结构&#xff0c;其中顶层的节点被称为根…

请求头/响应头、POST的使用方法、常用协议状态码

POST请求方法&#xff1a;添加资源 常用的几种数据格式&#xff1a; 1、XML的格式 2、表单数据格式 3、JSON数据格式 请求地址 请求方法 请求头 请求参数 COOKI E&#xff1a; 1、反爬虫 2、身份认证 Referer:请求是从哪个页面发送过来的 User-Agent&#xff1a;通…

机器学习---经验误差与过拟合、方差与偏差、性能度量、比较检验

1. 经验误差与过拟合 第三张图建立的模型&#xff0c;在训练集中通过x可以很好的预测y&#xff0c;然而我们不能预期该模型能够很好的预 测集外的数据&#xff0c;换句话说&#xff0c;这个模型没有很好的泛化能力。 第一张图建立了一个线性模型&#xff0c;但是该模型并没有…

PHP中常用数组排序算法

一&#xff1a;冒泡排序 1&#xff1a;算法步骤 比较相邻项的值&#xff0c;如果前者比后者大&#xff0c;交换顺序。 进行一轮比较后&#xff0c;最后一个值为最大的值。 进行下一轮比较&#xff0c;比上次少比较一项。 以此类推&#xff0c;比较剩下最后一项的时候&#…

【Hive】group by 分组聚合后使用窗口函数

文章目录 1. group by 分组聚合后使用排序窗口函数1.1 两种思路 与 简单例子1.2 新思路的解释 2. group by 分组聚合后使用聚合窗口函数3. group by 分组聚合后使用分析窗口函数 1. group by 分组聚合后使用排序窗口函数 1.1 两种思路 与 简单例子 group by 之后&#xff0c;…

AI时代带来的图片造假危机,该如何解决

一、前言 当今&#xff0c;图片造假问题非常泛滥&#xff0c;已经成为现代社会中一个严峻的问题。随着AI技术不断的发展&#xff0c;人们可以轻松地通过图像编辑和AI智能生成来篡改和伪造图片&#xff0c;使其看起来真实而难以辨别&#xff0c;之前就看到过一对硕士夫妻为了骗…

【Linux】进程信号 -- 信号保存与递达 | 信号捕捉 | 僵尸进程的信号处理方法

阻塞信号信号相关概念内核中的表示sigset_t信号集操作函数sigprocmasksigpending 小实验 - 观察pending表 信号的捕捉流程sigaction1.小实验&#xff1a;如果进程在处理2号信号&#xff0c;那我们继续发送2号信号会怎么样&#xff1f;2.如何正在处理这个信号&#xff0c;同时屏…

js - 关于防抖和节流函数的使用和细节

文章目录 一、什么是防抖二、应用场景三、实现原理1&#xff0c;第一个问题&#xff1a;为什么使用了闭包&#xff08;也就是说timer为什么定义到了外面&#xff09;2&#xff0c;第二个问题&#xff1a;防抖函数中this的指向问题&#xff1a; 四、节流函数 一、什么是防抖 事…

【【51单片机的I2C总线】】

51单片机的I2C总线 学会总线&#xff0c;掌控芯片。了解串口&#xff0c;真理全有。 I&#xff12;C时序 &#xff11;.起始条件&#xff1a;  SCL在高电平期间&#xff0c;SDA从高电平切换到低电平 终止条件&#xff1a; SCL在高电平期间&#xff0c;SDA从低电平切换为高电…

【go语言学习笔记】01 Go语言快速入门

文章目录 一、基础入门1. 示例程序2. 安装与环境变量设置3. 项目构建和编译发布3.1 go build和go install对比3.2 跨平台编译 二、数据类型1. 基础类型1.1 整型1.2 浮点数1.3 布尔型1.4 字符串1.5 零值 2. 变量2.1 变量声明2.1.1 指定变量类型2.1.2 根据值自行判定变量类型2.1.…

深入学习 Redis - 深挖经典数据类型之 list

目录 前言 一、list 类型 1.1、操作命令 lpush / rpush&#xff08;插入元素&#xff09; lrange&#xff08;查看范围元素&#xff09; lpushx / rpushx &#xff08;有约束的插入&#xff09; lpop / rpop&#xff08;头删尾删&#xff09; lindex&#xff08;获取下…

2023年第三届能源、电力与电气工程国际会议 (CoEEPE 2023)

会议简介 Brief Introduction 2023年第三届能源、电力与电气工程国际会议(CoEEPE 2023) 会议时间&#xff1a;2023年11月22日-24日 召开地点&#xff1a;澳大利亚墨尔本 大会官网&#xff1a;www.coeepe.org 2023年第三届能源、电力与电气工程国际会议(CoEEPE 2023)由安徽大学、…

使用GGML和LangChain在CPU上运行量化的llama2

Meta AI 在本周二发布了最新一代开源大模型 Llama 2。对比于今年 2 月发布的 Llama 1&#xff0c;训练所用的 token 翻了一倍&#xff0c;已经达到了 2 万亿&#xff0c;对于使用大模型最重要的上下文长度限制&#xff0c;Llama 2 也翻了一倍。 在本文&#xff0c;我们将紧跟趋…

JavaScript基础语法及小案例

目录 JavaScript基础语法1. 变量声明和赋值2. 数据类型1) 基本数据类型2) 复合数据类型(引用类型)3) 特殊数据类型 3. 运算符1) 算术运算符2) 赋值运算符3) 比较运算符4) 逻辑运算符5) 三元运算符 4. 控制流程1) 条件语句2) 循环语句 5. 函数1) 函数的基本使用① 什么是函数② …

DXFReader.NET 2023 Crack

DXFReader.NET 是一个 .NET 组件&#xff0c;允许直接从 AutoCAD 图形文件格式 DXF&#xff08;也称为图形交换格式&#xff09;查看、操作和打印。 DXFReader.NET 之 DXF 是 Drawing eXchange Format 的首字母缩写。DXF 是图形文件内容的复制&#xff0c;支持将文件从一个 CA…

机器学习深度学习——预备知识(上)

深大的夏令营已经结束&#xff0c;筛选入营的保研er就筛选了1/3&#xff0c;280多的入营总人数里面双非只有30左右。 最终虽然凭借机试拿到offer了&#xff0c;但是我感受到了自己的明显短板&#xff0c;比如夏令营的舍友就都有一篇核心论文&#xff0c;甚至还有SCI一区一作的。…

Mac应用程序因“来自身份不明的开发者”无法打开如何解决

相信不少mac电脑用户在安装应用程序时经常会遇到“xxx.app已损坏&#xff0c;打不开。这是mac系统的新的安全机制&#xff0c;安装 App 时提示&#xff1a; 常见的几种报错提示 xxx 已损坏&#xff0c;无法打开。您应该将它移到废纸篓打不开 xxx&#xff0c;因为它来自身份不明…

旋翼式水表安装注意事项

旋翼式水表是一种常用的水流计量设备&#xff0c;适用于小口径管道的单向水流总量的计量。如果你正在考虑安装旋翼式水表&#xff0c;以下是一些需要注意的事项&#xff1a; 1.安装位置的选择&#xff1a;旋翼式水表应该安装在管道的垂直方向上&#xff0c;并且水流方向必须与水…