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

基于Go/Grpc/kubernetes/Istio开发微服务的最佳实践尝试 - 1/3的更多相关文章

  1. 基于minikube的kubernetes集群部署及Vitess最佳实践

    简介 minikube是一个可以很容易在本地运行Kubernetes集群的工具, minikube在电脑上的虚拟机内运行单节点Kubernetes集群,可以很方便的供Kubernetes日常开发使用: ...

  2. Dubbo 入门系列之基于 Dubbo API 开发微服务应用

    目标 从零上手开发基于 Dubbo 的微服务 难度 低 环境要求 系统:Windows.Linux.MacOS JDK 8 及以上(推荐使用 JDK17) Git IntelliJ IDEA(可选) ...

  3. 社区活动分享PPT:使用微软开源技术开发微服务

    上周六在成都中生代技术社区线下活动进行了一个名为"微软爱开源-使用微软开源技术开发微服务"的技术分享. 也算是给很多不熟悉微软开源技术的朋友普及一下微软最近几年在开源方面所做的努力 ...

  4. 使用.NET Core+Docker 开发微服务

    .NET Core发布很久了,因为近几年主要使用java,所以还没使用过.NET Core,今天正好有一个c#写的demo,需要做成服务,不想再转成java来实现,考虑使用.NET CORE来尝下鲜, ...

  5. [学习笔记]尝试go-micro开发微服务<第一波>

    平时项目都是基于c++,lua,node, 现在打算开始自学开发微服务;   也顺带磨砺下go和docker 前期准备 1. 有golang编程基础 本系列文章是基于有golang编程基础,有过实际开 ...

  6. .net 与 java 开发微服务对比

    java+spring boot+maven对比.net 优势: 1. spring 自身带的ioc 比.net 更简单易用. 2. spring actuator的健康检测等运行时状态查看功能很赞. ...

  7. Spring boot 零配置开发微服务

    2018年12月29日星期六 体验Spring boot 零配置开发微服务 1.为什么要用Spring  boot? 1.1 简单方便.配置少.整合了大多数框架 1.2 适用于微服务搭建,搭建的微服务 ...

  8. 从Uber微服务看最佳实践如何炼成?

    导读:Uber成长非常迅速,工程师团队快速扩充,据说Uber有2000名工程师,8000个代码仓库,部署了1000多个微服务.微服务架构是Uber应对技术团队快速增长,功能快速上线很出色的解决方案.本 ...

  9. [转]在 Azure 云服务上设计大规模服务的最佳实践

    本文转自:http://technet.microsoft.com/zh-cn/magazine/jj717232.aspx 英文版:http://msdn.microsoft.com/library ...

  10. 基于消息队列 RocketMQ 的大型分布式应用上云最佳实践

    作者|绍舒 审核&校对:岁月.佳佳 编辑&排版:雯燕 前言 消息队列是分布式互联网架构的重要基础设施,在以下场景都有着重要的应用: 应用解耦 削峰填谷 异步通知 分布式事务 大数据处理 ...

随机推荐

  1. pytest之conftest.py

    一.conftest.py的特点 1.可以跨.py文件调用,有多个.py文件调用时,可让conftest.py只调用了一次fixture,或调用多次fixture 2.conftest.py与运行的用 ...

  2. 【APT】海莲花组织DLL样本分析

    前言 样本来源Twitter,之前的文章已经完整分析过一个类似的DLL样本,这次做个简单记录. 样本分析 样本信息如下: DLL文件共有40个导出函数: 导出函数内容基本一致,恶意代码都在DllMai ...

  3. 西瓜书3.4 解题报告(python 多分类学习 十折交叉法)

    偷懒找了UCI上最小的一个数据集,数据大约是集装箱起重机的转动速度.角度,判断其力量大小(我不懂起重机啊啊啊) 虽然不懂但并不妨碍写代码分类,显然标记就是力量,分为0.3.0.5.0.7三种.具体的模 ...

  4. Java 中的内存分配

    Java 中的内存分配 Java 程序运行时,需要在内存中分配空间.为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式. 一.栈:储存局部变量 局部变量 ...

  5. 蓝桥2020 B组 第一场考试

    2. 纪念日 问题描述: 请问从 1921 年 7 月 23 日中午 12 时到 2020 年 7 月 1 日中午 12 时一共包 含多少分钟? 答案提交: 这是一道结果填空题,你只需要算出结果后提交 ...

  6. 记住这个错误org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDAO is not known to the MapperRegistry.

    错误: org.apache.ibatis.binding.BindingException: Type interface com.kuang.dao.UserDAO is not known to ...

  7. 用js获取当前路由信息的方法

    1,设置或获取对象指定的文件名或路径.alert(window.location.pathname)2,设置或获取整个 URL 为字符串.alert(window.location.href);3,设 ...

  8. 【Unity】利用C#反射打印类的字段信息

    最近在用protobuf-net序列化功能生成.bytes配置文件时,遇到了需要把.bytes配置文件再另外转成Lua配置文件(Lua配置表内容举例)的需求.Lua配置文件需要记录配置类的各个字段名和 ...

  9. redis缓存lua脚本过多导致内存占用很多问题

    现象 生产某集群各节点已使用内存比较大,在清理了大量业务无用数据后,节点已使用内存却未下降. 排查与分析 通过info memory命令查看,内存碎片率略高,但是对某个主节点新添加的从节点内存使用依然 ...

  10. 实验二 K-近邻算法及应用

    博客班级 https://edu.cnblogs.com/campus/ahgc/machinelearning 作业要求 https://edu.cnblogs.com/campus/ahgc/ma ...