sponge 是一个微服务框架,一个快速创建web和微服务代码工具。sponge拥有丰富的生成代码命令,一共生成12种不同功能代码,这些功能代码可以组合成完整的服务(类似人为打散的海绵细胞可以自动重组成一个新的海绵)。微服务代码功能包括日志、服务注册与发现、注册中心、限流、熔断、链路跟踪、指标监控、pprof性能分析、统计、缓存、CICD等功能。代码解耦模块化设计,包括了从开发到部署完整工程,常用代码和脚本是自动生成,只需在按照代码模板去编写业务逻辑代码,使得开发效率提高不少。
github:https://github.com/zhufuyi/sponge
生成代码基于Yaml、SQL DDL和Protocol buffers三种方式,每种方式拥有生成不同功能代码,生成代码的框架图如图1-1所示:
图1-1 sponge生成代码框架图
在同一个文件夹内,如果发现最新生成代码和原来代码冲突,sponge会取消此次生成流程,不会对原来代码有任何修改,因此不必担心写的业务逻辑代码被覆盖问题。
sponge创建的微服务代码框架如图1-2所示,这是典型的微服务分层结构,具有高性能,高扩展性,包含常用的服务治理功能。
微服务主要功能:
代码目录结构遵循 project-layout,代码目录结构如下所示:
.
├── api # proto文件和生成的*pb.go目录
├── assets # 其他与资源库一起使用的资产(图片、logo等)目录
├── build # 打包和持续集成目录
├── cmd # 程序入口目录
├── configs # 配置文件的目录
├── deployments # IaaS、PaaS、系统和容器协调部署的配置和模板目录
├─ docs # 设计文档和界面文档目录
├── internal # 私有应用程序和库的代码目录
│ ├── cache # 基于业务包装的缓存目录
│ ├── config # Go结构的配置文件目录
│ ├── dao # 数据访问目录
│ ├── ecode # 自定义业务错误代码目录
│ ├── handler # http的业务功能实现目录
│ ├── model # 数据库模型目录
│ ├── routers # http路由目录
│ ├── rpcclient # 连接rpc服务的客户端目录
│ ├── server # 服务入口,包括http、rpc等
│ ├── service # rpc的业务功能实现目录
│ └── types # http的请求和响应类型目录
├── pkg # 外部应用程序可以使用的库目录
├── scripts # 用于执行各种构建、安装、分析等操作的脚本目录
├── test # 额外的外部测试程序和测试数据
└── third_party # 外部帮助程序、分叉代码和其他第三方工具
web服务和rpc服务目录结构基本一致,其中有一些目录是web服务独有(internal目录下的routers、handler、types),有一些目录是rpc服务独有(internal目录下的service)。
如果使用windows环境,需要先安装相关依赖工具,其他环境忽略即可。
(1) 安装mingw64
mingw64是Minimalist GNUfor Windows的缩写,它是一个可自由使用和自由发布的Windows特定头文件和使用GNU工具集导入库的集合,下载预编译源码生成的二进制文件,下载地址:
https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z
下载后解压到D:\Program Files\mingw64
目录下,修改系统环境变量PATH,新增D:\Program Files\mingw64\bin
。
安装make命令
切换到D:\Program Files\mingw64\bin
目录,找到mingw32-make.exe
可执行文件,复制并改名为make.exe
。
(2) 安装cmder
cmder 是一个增强型命令行工具,包含一些sponge依赖的命令(bash、git等),cmder下载地址:
https://github.com/cmderdev/cmder/releases/download/v1.3.20/cmder.zip
下载后解压到D:\Program Files\cmder
目录下,修改系统环境变量PATH,新增D:\Program Files\cmder
。
(1) 安装 go
下载地址 https://go.dev/dl/ 或 https://golang.google.cn/dl/ 选择版本(>=1.16)安装,把 $GOROOT/bin
添加到系统path下。
注:如果没有科学上网,建议设置国内代理 go env -w GOPROXY=https://goproxy.cn,direct
(2) 安装 protoc
下载地址 https://github.com/protocolbuffers/protobuf/releases/tag/v3.20.3 ,把protoc文件所在目录添加系统path下。
(3) 安装 sponge
执行命令:
go install github.com/zhufuyi/sponge/cmd/sponge@latest
注:sponge二进制文件所在目录必须在系统path下。
(4) 安装依赖插件和工具
执行命令:
sponge init
执行命令后自动安装了依赖插件和工具:protoc-gen-go、 protoc-gen-go-grpc、 protoc-gen-validate、 protoc-gen-gotag、 protoc-gen-go-gin、 protoc-gen-go-rpc-tmpl、 protoc-gen-openapiv2、 protoc-gen-doc、 golangci-lint、 swag、 go-callvis
如果有依赖工具安装出错,执行命令重试:
sponge tools --install
查看依赖工具安装情况:
sponge tools
所有依赖工具和插件:
Installed dependency tools:
✔ go
✔ protoc
✔ protoc-gen-go
✔ protoc-gen-go-grpc
✔ protoc-gen-validate
✔ protoc-gen-gotag
✔ protoc-gen-go-gin
✔ protoc-gen-go-rpc-tmpl
✔ protoc-gen-openapiv2
✔ protoc-gen-doc
✔ swag
✔ golangci-lint
✔ go-callvis
sponge命令的帮助信息有详细的使用示例,在命令后面添加-h
查看,例如sponge web model -h
,这是根据mysql表生成gorm的model代码返回的帮助信息。
安装完成sponge和依赖工具后,就可以开始使用了,sponge生成代码提供命令行和UI界面两种方式,其实通过UI界面方式,在后台也是执行一样的命令。sponge UI界面方式支持记忆最近一次下载代码的参数,使用起来更加方便,启动UI服务:
sponge run
在浏览器访问 http://localhost:24631
,打开主页如图2-1所示,根据实际需要生成代码,代码是通过浏览器下载到本地,因此也可以把sponge的UI服务部署到服务器长期运行。
图2-1 sponge 的ui界面
虽然界面化命令更加方便,但是对于部分命令使用命令行方式更节省操作,例如:根据新的数据表或新的proto文件生成代码,直接一个命令就可以完成,下面介绍各种生成代码功能都是使用命令行方式。
根据mysql的数据表来生成代码,先准备一个mysql服务(docker安装mysql),例如mysql有一个数据库school,数据库下有一个数据表teacher,如下面sql所示:
CREATE DATABASE IF NOT EXISTS school DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci;
use school;
create table teacher
(
id bigint unsigned auto_increment
primary key,
created_at datetime null,
updated_at datetime null,
deleted_at datetime null,
name varchar(50) not null comment '用户名',
password varchar(100) not null comment '密码',
email varchar(50) not null comment '邮件',
phone varchar(30) not null comment '手机号码',
avatar varchar(200) not null comment '头像',
gender tinyint not null comment '性别,1:男,2:女,其他值:未知',
age tinyint not null comment '年龄',
birthday varchar(30) not null comment '出生日期',
school_name varchar(50) not null comment '学校名称',
college varchar(50) not null comment '学院',
title varchar(10) not null comment '职称',
profile text not null comment '个人简介'
)
comment '老师';
create index teacher_deleted_at_index
on teacher (deleted_at);
把SQL DDL导入mysql创建一个数据库school,school下面有一个表teacher。
打开终端,执行命令:
sponge web http \
--module-name=edusys \
--server-name=edusys \
--project-name=edusys \
--repo-addr=zhufuyi \
--db-dsn=root:123456@(192.168.3.37:3306)/school \
--db-table=teacher \
--out=./edusys
查看参数说明命令sponge web http -h
,注意参数repo-addr是镜像仓库地址,默认值是 image-repo-host
,这个参数用在构建 docker 镜像、k8s 的部署脚本上,如果使用docker官方镜像仓库,只需填写注册docker仓库的用户名,如果使用私有仓库地址,需要填写完整仓库地址。
生成完整的http服务代码是在当前目录edusys下,目录结构如下:
.
├── build
├── cmd
│ └── edusys
│ └── initial
├── configs
├── deployments
│ ├── docker-compose
│ └── kubernetes
├── docs
├── internal
│ ├── cache
│ ├── config
│ ├── dao
│ ├── ecode
│ ├── handler
│ ├── model
│ ├── routers
│ ├── server
│ └── types
└── scripts
在edusys目录下的Makefile文件,集成了编译、测试、运行、部署等相关命令,切换到edusys目录下运行服务:
# 更新swagger文档
make docs
# 编译和运行服务
make run
复制 http://localhost:8080/swagger/index.html 到浏览器测试CRUD接口,如图3-1所示。
服务默认只开启了指标采集接口、每分钟的资源统计信息,其他服务治理默认是关闭。在实际应用中,根据需要做一些操作:
configs/edusys.yml
,把cacheType字段值改为redis,并且填写redis配置地址和端口。configs/edusys.yml
开启相关功能,如果开启链路跟踪功能,必须填写jaeger配置信息;如果开启服务注册与发现功能,必须填写consul、etcd、nacos其中一种配置信息。sponge config --server-dir=./edusys
更新对应的go struct,只修改字段值不需要执行更新命令。ingernal/ecode/teacher_http.go
,修改变量teacherNO值,这是唯一不重复的数值,返回信息说明根据自己需要修改,对teacher表操作的接口自定义错误码都在这里添加。一个服务中,通常不止一个数据表,如果添加了新数据表,生成的handler代码如何自动填充到已存在的服务代码中呢,需要用到sponge web handler
命令,例如添加了两个新数据表course和teach:
create table course
(
id bigint unsigned auto_increment
primary key,
created_at datetime null,
updated_at datetime null,
deleted_at datetime null,
code varchar(10) not null comment '课程代号',
name varchar(50) not null comment '课程名称',
credit tinyint not null comment '学分',
college varchar(50) not null comment '学院',
semester varchar(20) not null comment '学期',
time varchar(30) not null comment '上课时间',
place varchar(30) not null comment '上课地点'
)
comment '课程';
create index course_deleted_at_index
on course (deleted_at);
create table teach
(
id bigint unsigned auto_increment
primary key,
created_at datetime null,
updated_at datetime null,
deleted_at datetime null,
teacher_id bigint not null comment '老师id',
teacher_name varchar(50) not null comment '老师名称',
course_id bigint not null comment '课程id',
course_name varchar(50) not null comment '课程名称',
score char(5) not null comment '学生评价教学质量,5个等级:A,B,C,D,E'
)
comment '老师课程';
create index teach_course_id_index
on teach (course_id);
create index teach_deleted_at_index
on teach (deleted_at);
create index teach_teacher_id_index
on teach (teacher_id);
生成包含CRUD业务逻辑的handler代码:
sponge web handler \
--db-dsn=root:123456@(192.168.3.37:3306)/school \
--db-table=course,teach \
--out=./edusys
查看参数说明命令sponge web handler -h
,参数out
是指定已存在的服务文件夹edusys,如果参数out
为空,必须指定module-name
参数,在当前目录生成handler子模块代码,然后把handler代码复制到文件夹edusys,两种方式效果都一样。
执行命令后,在edusys/internal
目录下生成了course和teach相关的代码:
.
└── internal
├── cache
├── dao
├── ecode
├── handler
├── model
├── routers
└── types
切换到edusys目录下执行命令运行服务:
# 更新swagger文档
make docs
# 编译和运行服务
make run
复制 http://localhost:8080/swagger/index.html 到浏览器测试CRUD接口,如图3-2所示。
图3-2 http swagger文档界面
实际使用中需要修改自定义的CRUD接口返回错误码和信息,打开文件ingernal/ecode/course_http.go
修改变量courseNO值,打开文件ingernal/ecode/teach_http.go
修改变量teachNO值。
虽然生成了每个数据表的CRUD接口,不一定适合实际业务逻辑,就需要手动添加业务逻辑代码了,数据库操作代码填写到internal/dao
目录下,业务逻辑代码填写到internal/handler
目录下。
如果不需要标准CRUD接口的http服务代码,可以在proto文件自定义接口,使用spong命令生成http服务和接口模板代码。
protocol buffers 语法规则看官方文档 ,下面是一个示例文件 teacher.proto 内容,每个方法定义了路由和swagger文档的描述信息,实际应用中根据需要在 message 添加 tag 和 validate 的描述信息。
syntax = "proto3";
package api.edusys.v1;
import "google/api/annotations.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
option go_package = "edusys/api/edusys/v1;v1";
// 生成*.swagger.json基本信息
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
host: "localhost:8080"
base_path: ""
info: {
title: "edusys api docs";
version: "v0.0.0";
};
schemes: HTTP;
schemes: HTTPS;
consumes: "application/json";
produces: "application/json";
};
service teacher {
rpc Register(RegisterRequest) returns (RegisterReply) {
// 设置路由
option (google.api.http) = {
post: "/api/v1/Register"
body: "*"
};
// 设置路由对应的swagger文档
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "注册用户",
description: "提交信息注册",
tags: "teacher",
};
}
rpc Login(LoginRequest) returns (LoginReply) {
option (google.api.http) = {
post: "/api/v1/login"
body: "*"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "登录",
description: "登录",
tags: "teacher",
};
}
}
message RegisterRequest {
string name = 1;
string email = 2;
string password = 3;
}
message RegisterReply {
int64 id = 1;
}
message LoginRequest {
string email = 1;
string password = 2;
}
message LoginReply {
string token = 1;
}
打开终端,执行命令:
sponge web http-pb \
--module-name=edusys \
--server-name=edusys \
--project-name=edusys \
--repo-addr=zhufuyi \
--protobuf-file=./teacher.proto \
--out=./edusys
查看参数说明命令 sponge web http-pb -h
,支持*号匹配(示例--protobuf-file=*.proto
),表示根据批量proto文件生成代码,多个proto文件中至少包括一个service,否则不允许生成代码。
生成http服务代码的目录如下所示,与sponge web http
生成的http服务代码目录有一些区别,新增了proto文件相关的api和third_party目录,internal目录下没有cache、dao、model、handler、types目录,其中handler是存放业务逻辑模板代码目录,通过命令会自动生成。
.
├── api
│ └── edusys
│ └──v1
├── build
├── cmd
│ └── edusys
│ └── initial
├── configs
├── deployments
│ ├── docker-compose
│ └── kubernetes
├── docs
├── internal
│ ├── config
│ ├── ecode
│ ├── routers
│ └── server
├── scripts
└── third_party
切换到edusys目录下执行命令运行服务:
# 生成*pb.go文件、生成handler模板代码、更新swagger文档
make proto
# 编译和运行服务
make run
复制 http://localhost:8080/apis/swagger/index.html 到浏览器测试接口,如图3-3所示,请求会返回500错误,因为模板代码(internal/handler/teacher_logic.go文件)直接调用panic("implement me")
,这是为了提示要填写业务逻辑代码。
根据业务需求,需要添加新接口,分为两种情况:
(1) 在原来proto文件添加新接口
打开 api/edusys/v1/teacher.proto
,例如添加bindPhone方法,并填写路由和swagger文档描述信息,完成添加一个新接口。
执行命令:
# 生成*pb.go文件、生成handler模板代码、更新swagger文档
make proto
在internal/handler
和internal/ecode
两个目录下生成新的模板文件,然后复制最新生成的模板代码到业务逻辑代码区:
internal/handler
目录下生成了后缀为 .gen.日期时间 模板代码文件(示例teacher_logic.go.gen.xxxx225619),因为teacher_logic.go已经存在,不会把写的业务逻辑代码覆盖,所以生成新文件。打开文件 teacher_logic.go.gen.xxxx225619,把添加方法bindPhone接口的模板代码复制到teacher_logic.go文件中,然后填写业务逻辑代码。internal/ecode
目录下生成了后缀为 .gen.日期时间 模板代码文件,把 bindPhone 接口错误码复制到 teacher_http.go 文件中。(2) 在新proto文件添加接口
例如新添加了course.proto文件,course.proto下的接口必须包括路由和swagger文档描述信息,查看 章节3.2.1,把course.proto文件复制到api/edusys/v1
目录下,完成新添加接口。
执行命令:
# 生成*pb.go文件、生成handler模板代码、更新swagger文档
make proto
在 internal/handler
、internal/ecode
、 internal/routers
三个目录下生成course名称前缀的代码文件,只需做下面两个操作:
internal/handler/course.go
文件填写业务代码。internal/ecode/course_http.go
文件修改自定义错误码和信息说明。sponge web http-pb
命令生成的http服务代码没有dao
、cache
、model
等操作数据的相关代码,使用者可以自己实现,如果使用mysql数据库和redis缓存,可以使用sponge工具直接生成dao
、cache
、model
代码。
生成CRUD操作数据库代码命令:
sponge web dao \
--db-dsn=root:123456@(192.168.3.37:3306)/school \
--db-table=teacher \
--include-init-db=true \
--out=./edusys
查看参数说明命令 sponge web dao -h
,参数--include-init-db
在一个服务中只使用一次,下一次生成dao
代码时去掉参数--include-init-db
,否则会造成无法生成最新的dao
代码,原因是db初始化代码已经存在。
使用sponge生成的dao
代码,需要做一些操作:
cmd/edusys/initial/initApp.go
文件,把调用mysql和redis初始化代码反注释掉,打开cmd/edusys/initial/registerClose.go
文件,把调用mysql和redis释放资源代码反注释掉,初始代码是一次性更改。dao
代码,并不能和自定义方法register和login完全对应,需要手动在文件internal/dao/teacher.go
补充代码,然后在internal/handler/teacher.go
填写业务逻辑代码,业务代码中返回错误使用internal/ecode
目录下定义的错误码,如果直接返回错误信息,请求端会收到unknown错误信息,也就是未定义错误信息。configs/edusys.yml
修改字段cacheType值为redis,并填写redis地址和端口。切换到edusys目录下再次运行服务:
go mod tidy
# 编译和运行服务
make run
打开 http://localhost:8080/apis/swagger/index.html 再次请求接口,可以正常返回数据了。
生成http服务代码有mysql和proto文件两种方式:
sponge web dao
命令生成操作数据库代码。添加了新的接口,除了生成handler模板代码,swagger文档、路由注册代码、接口的错误码会自动生成。两种方式都可以完成同样的http服务接口,根据实际应用选择其中一种,如果做后台管理服务,使用mysql直接生产CRUD接口代码,可以少写代码。对于多数需要自定义接口服务,使用proto文件方式生成的http服务,这种方式自由度也比较高,写好proto文件之后,除了业务逻辑代码,其他代码都是通过插件生成。
以 章节3.1.1 的teacher表为例,创建rpc服务:
sponge micro rpc \
--module-name=edusys \
--server-name=edusys \
--project-name=edusys \
--repo-addr=zhufuyi \
--db-dsn=root:123456@(192.168.3.37:3306)/school \
--db-table=teacher \
--out=./edusys
查看参数说明命令 sponge micro rpc -h
,生成rpc服务代码在当前edusys目录下,目录结构如下:
.
├── api
│ ├── edusys
│ │ └── v1
│ └── types
├── build
├── cmd
│ └── edusys
│ └── initial
├── configs
├── deployments
│ ├── docker-compose
│ └── kubernetes
├── docs
├── internal
│ ├── cache
│ ├── config
│ ├── dao
│ ├── ecode
│ ├── model
│ ├── server
│ └── service
├── scripts
└── third_party
在edusys目录下的Makefile文件,集成了编译、测试、运行、部署等相关命令,切换到edusys目录下执行命令运行服务:
# 生成*pb.go
make proto
# 编译和运行服务
make run
rpc服务包括了CRUD逻辑代码,也包括rpc客户端测试和压测代码,使用Goland或VS Code打开internal/service/teacher_client_test.go
文件,
图4-1 性能测试报告界面
从服务启动日志看到默认监听8282端口(rpc服务)和8283端口(采集metrics或profile),开启了每分钟的打印资源统计信息。在实际应用中,根据需要做一些修改:
configs/edusys.yml
,把cacheType字段值改为redis,并且填写redis配置地址和端口。configs/edusys.yml
开启相关功能,如果开启链路跟踪功能,需要填写jaeger配置信息,如果开启服务注册与发现功能,需要填写consul、etcd、nacos其中一种配置信息。sponge config --server-dir=./edusys
更新对应的go struct,只修改字段值不需要执行更新命令。ingernal/ecode/teacher_rpc.go
,修改变量teacherNO值(数值唯一),返回信息说明根据自己需要修改,对teacher表操作的接口错误信息都在这里添加。添加了两个新表course和teach,数据表的结构看 章节3.1.3,生成service代码:
sponge micro service \
--db-dsn=root:123456@(192.168.3.37:3306)/school \
--db-table=course,teach \
--out=./edusys
查看参数说明命令 sponge micro service -h
,参数out
指定已存在的rpc服务文件夹edusys,如果参数out
为空,必须指定module-name
和server-name
两个参数,在当前目录下生成service代码,然后手动复制到文件夹edusys,两种方式效果都一样。
执行命令后,在下面目录下生成了course和teach相关的代码,如果添加自定义方法或新的protocol buffers文件,也是在下面目录手动添加代码。
.
├── api
│ └── edusys
│ └── v1
└── internal
├── cache
├── dao
├── ecode
├── model
└── service
切换到edusys目录下执行命令运行服务:
# 更新*.pb.go
make proto
# 编译和运行服务
make run
使用Goland或VS Code打开internal/service/course_client_test.go
和internal/service/teach_client_test.go
文件测试CRUD方法,测试前需要先填写参数。
sponge不仅支持基于mysql创建rpc服务,还支持基于proto文件生成rpc服务。
下面是一个proto示例文件 teacher.proto 内容:
syntax = "proto3";
package api.edusys.v1;
option go_package = "edusys/api/edusys/v1;v1";
service teacher {
rpc Register(RegisterRequest) returns (RegisterReply) {}
rpc Login(LoginRequest) returns (LoginReply) {}
}
message RegisterRequest {
string name = 1;
string email = 2;
string password = 3;
}
message RegisterReply {
int64 id = 1;
}
message LoginRequest {
string email = 1;
string password = 2;
}
message LoginReply {
string token = 1;
}
打开终端,执行命令:
sponge micro rpc-pb \
--module-name=edusys \
--server-name=edusys \
--project-name=edusys \
--repo-addr=zhufuyi \
--protobuf-file=./teacher.proto \
--out=./edusys
查看参数说明命令sponge micro rpc-pb -h
,支持*号匹配(示例--protobuf-file=*.proto
),表示根据批量proto文件生成代码,多个proto文件中至少包括一个service,否则无法生成代码。
生成rpc服务代码目录如下所示,与sponge micro rpc
生成的rpc服务代码目录有一些区别,internal目录下没有cache、dao、model子目录。
.
├── api
│ └── edusys
│ └── v1
├── build
├── cmd
│ └── edusys
│ └── initial
├── configs
├── deployments
│ ├── docker-compose
│ └── kubernetes
├── docs
├── internal
│ ├── config
│ ├── ecode
│ ├── server
│ └── service
├── scripts
└── third_party
切换到edusys目录下执行命令运行服务:
# 生成*pb.go文件、生成service模板代码
make proto
# 编译和运行服务
make run
启动rpc服务之后,使用Goland或VS Code打开internal/service/teacher_client_test.go
文件,对 Test_teacher_methods 下各个方法进行测试,测试前要先填写测试参,会发现请求返回内部错误,因为在模板代码文件internal/service/teacher.go
插入了代码panic("implement me")
,这是为了提示要填写业务逻辑代码。
根据业务需求,需要添加新方法,分为两种情况操作:
(1) 在原来proto文件添加新方法
打开 api/edusys/v1/teacher.proto
,例如添加bindPhone方法。
执行命令:
# 生成*pb.go文件、生成service模板代码
make proto
在 internal/service
和internal/ecode
两个目录下生成模板代码,然后复制模板代码到业务逻辑代码区:
internal/service
目录下生成了后缀为 .gen.日期时间 模板代码文件(示例teacher.go.gen.xxxx225732),因为teacher.go已经存在,不会把原来写的业务逻辑代码覆盖,所以生成了新的文件,打开文件 teacher.go.gen.xxxx225732,把添加bindPhone方法的模板代码复制到teacher.go文件中,然后填写业务逻辑代码。internal/ecode
目录下生成了后缀为 teacher_rpc.go.gen.日期时间 文件,把bindPhone方法对应的错误码复制到teacher_rpc.go文件中。(2) 在新的proto文件添加新方法
例如新添加了course.proto文件,把course.proto文件复制到api/edusys/v1
目录下,完成新添加接口。
执行命令:
# 生成*pb.go文件、生成service模板代码
make proto
在 internal/service
、internal/ecode
、 internal/routers
三个目录下生成course名称前缀的代码文件,只需做下面两个操作:
internal/service/course.go
文件填写业务代码。internal/ecode/course_rpc.go
文件修改自定义错误码和信息说明。sponge micro rpc-pb
命令生成的rpc服务代码没有dao
、cache
、model
等操作数据的相关代码,使用者可以自己实现,如果使用mysql数据库和redis缓存,可以使用sponge工具直接生成dao
、cache
、model
代码。
生成CRUD操作数据库代码命令:
sponge micro dao \
--db-dsn=root:123456@(192.168.3.37:3306)/school \
--db-table=teacher \
--include-init-db=true \
--out=./edusys
查看参数说明命令sponge micro dao -h
,参数--include-init-db
在一个服务中只使用一次,下一次生成dao
代码时去掉参数--include-init-db
,否则会造成无法生成最新的dao
代码。
使用sponge生成的dao
代码,需要做一些操作:
cmd/edusys/initial/initApp.go
文件,把调用mysql和redis初始化代码反注释掉,打开cmd/edusys/initial/registerClose.go
文件,把调用mysql和redis释放资源代码反注释掉,初始代码是一次性更改。dao
代码,并不能和自定义方法register和login完全对应,需要手动在文件internal/dao/teacher.go
补充代码,然后在internal/handler/teacher.go
填写业务逻辑代码,业务代码中返回错误使用internal/ecode
目录下定义的错误码,如果直接返回错误信息,请求端会收到unknown错误信息,也就是未定义错误信息。configs/edusys.yml
修改字段cacheType值为redis,并填写redis地址和端口。切换到edusys目录下再次运行服务:
go mod tidy
# 编译和运行服务
make run
启动rpc服务之后,使用Goland或VS Code打开internal/service/teacher_client_test.go
文件测试各个方法。
微服务通常提供的是细粒度的API,实际提供给客户端是粗粒度的API,需要从不同微服务获取数据聚合在一起组成符合实际需求的API,这是rpc gateway的作用,rpc gateway本身也是一个http服务,如图4-2所示。
图4-2 rpc gateway框架图
以电商微服务为例,商品详情页面有商品、库存、商品评价等信息,这些信息保存在不同的微服务中,一般很少请求每个微服务获取数据,直接请求微服务会造成网络压力倍增,通常的做法是聚合多个微服务数据一次性返回。
下面四个文件夹,每个文件夹下都有一个简单的proto文件。
.
├── comment
│ └── v1
│ └──comment.proto
├── inventory
│ └── v1
│ └── inventory.proto
├── product
│ └── v1
│ └── product.proto
└── shopgw
└── v1
└── shopgw.proto
comment.proto文件内容如下:
syntax = "proto3";
package api.comment.v1;
option go_package = "shopgw/api/comment/v1;v1";
service Comment {
rpc ListByProductID(ListByProductIDRequest) returns (ListByProductIDReply) {}
}
message ListByProductIDRequest {
int64 productID = 1;
}
message CommentDetail {
int64 id=1;
string username = 2;
string content = 3;
}
message ListByProductIDReply {
int32 total = 1;
int64 productID = 2;
repeated CommentDetail commentDetails = 3;
}
inventory.proto文件内容如下:
syntax = "proto3";
package api.inventory.v1;
option go_package = "shopgw/api/inventory/v1;v1";
service Inventory {
rpc GetByID(GetByIDRequest) returns (GetByIDReply) {}
}
message GetByIDRequest {
int64 id = 1;
}
message InventoryDetail {
int64 id = 1;
float num = 4;
int32 soldNum =3;
}
message GetByIDReply {
InventoryDetail inventoryDetail = 1;
}
product.proto文件内容如下:
syntax = "proto3";
package api.product.v1;
option go_package = "shopgw/api/product/v1;v1";
service Product {
rpc GetByID(GetByIDRequest) returns (GetByIDReply) {}
}
message GetByIDRequest {
int64 id = 1;
}
message ProductDetail {
int64 id = 1;
string name = 2;
float price = 3;
string description = 4;
}
message GetByIDReply {
ProductDetail productDetail = 1;
int64 inventoryID = 2;
}
shopgw.proto文件内容如下,rpc网关服务的proto和其他rpc服务的proto有一点区别,需要指定方法的路由和swagger的描述信息。
syntax = "proto3";
package api.shopgw.v1;
import "api/product/v1/product.proto";
import "api/comment/v1/comment.proto";
import "api/inventory/v1/inventory.proto";
import "google/api/annotations.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
option go_package = "shopgw/api/shopgw/v1;v1";
// default settings for generating *.swagger.json documents
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
host: "localhost:8080"
base_path: ""
info: {
title: "eshop api docs";
version: "v0.0.0";
};
schemes: HTTP;
schemes: HTTPS;
consumes: "application/json";
produces: "application/json";
};
service ShopGw {
rpc GetDetailsByProductID(GetDetailsByProductIDRequest) returns (GetDetailsByProductIDReply) {
option (google.api.http) = {
get: "/api/v1/detail"
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "get detail",
description: "get detail from product id",
tags: "shopgw",
};
}
}
message GetDetailsByProductIDRequest {
int64 productID = 1;
}
message GetDetailsByProductIDReply {
api.product.v1.ProductDetail productDetail = 1;
api.inventory.v1.InventoryDetail inventoryDetail = 2;
repeated api.comment.v1.CommentDetail commentDetails = 3;
}
根据shopgw.proto文件生成rpc gateway服务代码:
sponge micro rpc-gw-pb \
--module-name=shopgw \
--server-name=shopgw \
--project-name=eshop \
--repo-addr=zhufuyi \
--protobuf-file=./shopgw/v1/shopgw.proto \
--out=./shopgw
查看参数说明命令 sponge micro rpc-gw-pb -h
,生成的rpc gateway服务代码在当前shopgw目录下,目录结构如下:
.
├── api
│ └── shopgw
│ └── v1
├── build
├── cmd
│ └── shopgw
│ └── initial
├── configs
├── deployments
│ ├── docker-compose
│ └── kubernetes
├── docs
├── internal
│ ├── config
│ ├── ecode
│ ├── routers
│ ├── rpcclient
│ └── server
├── scripts
└── third_party
因为product.proto依赖product.proto、inventory.proto、comment.proto文件,复制三个依赖的proto文件到api目录下,api目录结构如下:
.
├── comment
│ └── v1
│ └── comment.proto
├── inventory
│ └── v1
│ └── inventory.proto
├── product
│ └── v1
│ └── product.proto
└── shopgw
└── v1
└── shopgw.proto
切换到shopgw目录下运行服务:
# 生成*pb.go文件、生成模板代码、更新swagger文档
make proto
# 编译和运行服务
make run
复制 http://localhost:8080/apis/swagger/index.html 到浏览器测试接口,如图4-3所示。请求会返回500错误,因为模板代码(internal/service/shopgw_logic.go文件)直接调用panic("implement me")
,这是为了提示要填写业务逻辑代码。
图4-3 rpc gateway的swagger文档界面
(1) 生成连接rpc服务端代码
服务还没有连接rpc服务代码,下面是生成连接product、inventory、comment三个rpc服务的客户端代码命令:
sponge micro rpc-cli \
--rpc-server-name=comment,inventory,product \
--out=./shopgw
查看参数说明命令 sponge micro rpc-cli -h
,参数out
指定已存在的服务文件夹shopgw,生成的代码在internal/rpcclent
目录下。
(2) 初始化和关闭rpc连接
连接rpc服务端代码包括了初始化和关闭函数,根据调用模板代码填写:
cmd/shopgw/initial/initApp.go
文件的代码段// initializing the rpc server connection
下,根据模板调用初始化函数。cmd/shopgw/initial/registerClose.go
文件的代码段// close the rpc client connection
下,根据模板调用释放资源函数。(3) 修改配置
连接product、inventory、comment三个rpc服务代码已经有了,但rpc服务地址还没配置,需要在配置文件configs/shopgw.yml
的字段grpcClient
下添加连接product、inventory、comment三个微服务配置信息:
grpcClient:
- name: "product"
host: "127.0.0.1"
port: 8201
registryDiscoveryType: ""
- name: "inventory"
host: "127.0.0.1"
port: 8202
registryDiscoveryType: ""
- name: "comment"
host: "127.0.0.1"
port: 8203
registryDiscoveryType: ""
如果rpc服务使用了注册与发现,字段registryDiscoveryType
填写服务注册发现类型,支持consul、etcd、nacos三种。
生成对应go struct代码:
sponge config --server-dir=./shopgw
(4) 填写业务代码
下面是在模板文件internal/service/shopgw_logic.go
填写的业务逻辑代码示例,分别从product、inventory、comment三个rpc服务获取数据聚合在一起返回。
package service
import (
"context"
commentV1 "shopgw/api/comment/v1"
inventoryV1 "shopgw/api/inventory/v1"
productV1 "shopgw/api/product/v1"
shopgwV1 "shopgw/api/shopgw/v1"
"shopgw/internal/rpcclient"
)
var _ shopgwV1.ShopGwLogicer = (*shopGwClient)(nil)
type shopGwClient struct {
productCli productV1.ProductClient
inventoryCli inventoryV1.InventoryClient
commentCli commentV1.CommentClient
}
// NewShopGwClient creating rpc clients
func NewShopGwClient() shopgwV1.ShopGwLogicer {
return &shopGwClient{
productCli: productV1.NewProductClient(rpcclient.GetProductRPCConn()),
inventoryCli: inventoryV1.NewInventoryClient(rpcclient.GetInventoryRPCConn()),
commentCli: commentV1.NewCommentClient(rpcclient.GetCommentRPCConn()),
}
}
func (c *shopGwClient) GetDetailsByProductID(ctx context.Context, req *shopgwV1.GetDetailsByProductIDRequest) (*shopgwV1.GetDetailsByProductIDReply, error) {
productRep, err := c.productCli.GetByID(ctx, &productV1.GetByIDRequest{
Id: req.ProductID,
})
if err != nil {
return nil, err
}
inventoryRep, err := c.inventoryCli.GetByID(ctx, &inventoryV1.GetByIDRequest{
Id: productRep.InventoryID,
})
if err != nil {
return nil, err
}
commentRep, err := c.commentCli.ListByProductID(ctx, &commentV1.ListByProductIDRequest{
ProductID: req.ProductID,
})
if err != nil {
return nil, err
}
return &shopgwV1.GetDetailsByProductIDReply{
ProductDetail: productRep.ProductDetail,
InventoryDetail: inventoryRep.InventoryDetail,
CommentDetails: commentRep.CommentDetails,
}, nil
}
再次启动服务:
go mod tidy
# 编译和运行服务
make run
在浏览器访问 http://localhost:8080/apis/swagger/index.html ,请求返回503错误(服务不可用),原因是product、inventory、comment三个rpc服务都还没运行。
product、inventory、comment三个rpc服务代码都还没有,如何正常启动呢。这三个rpc服务的proto文件已经有了,根据章节 4.2 根据proto文件创建rpc服务 步骤生成代码和启动服务就很简单了。
生成rpc服务代码是基于mysql和proto文件两种方式,根据proto文件方式除了支持生成rpc服务代码,还支持生成rpc gateway服务(http)代码:
sponge web dao
命令生成操作数据库代码,根据proto文件生成service模板代码,在模板代码填充业务逻辑代码。sponge micro rpc-cli
命令使用。根据实际场景选择生成对应服务代码,如果主要是对数据表增删改查,根据mysql生成rpc服务可以少写代码;如果更多的是自定义方法,根据proto生成rpc服务更合适;rpc转http使用rpc gateway服务。
链路跟踪使用jaeger,存储使用elasticsearch,在本地使用docker-compose启动两个服务。
(1) elasticsearch服务
这是 elasticsearch服务的启动脚本,.env文件是elasticsearch的启动配置,启动elasticsearch服务:
docker-compose up -d
(2) jaeger服务
这是 jaeger服务的启动脚本,.env文件是配置jaeger信息,启动jaeger服务:
docker-compose up -d
在浏览器访问jaeger查询主页 http://localhost:16686 。
以 章节3.1.2 创建的http服务代码为例,修改配置文件configs/edusys.yml
,开启链路跟踪功能(字段enableTrace),并且填写jaeger配置信息。
如果想跟踪redis,启用redis缓存,把缓存类型字段cacheType值改为redis,并配置redis配置,同时在本地使用docker启动redis服务,这是redis服务启动脚本。
启动http服务:
# 编译和运行服务
make run
复制 http://localhost:8080/swagger/index.html 到浏览器访问swagger主页,以请求get查询为例,连续请求同一个id两次,链路跟踪如图5-1所示。
图5-1 单服务链路跟踪页面
从图中可以看到第一次请求有4个span,分别是:
说明第一次请求从redis查找,没有命中缓存,然后从mysql读取数据,最后置缓存。
第二次请求只有2个span,分别是:
说明第二次请求直接命中缓存,比第一次少了查询mysql和设置缓存过程。
这些span是自动生成的,很多时候需要手动添加自定义span,添加span示例:
import "github.com/zhufuyi/sponge/pkg/tracer"
tags := map[string]interface{}{"foo": "bar"}
_, span := tracer.NewSpan(ctx, "spanName", tags)
defer span.End()
以章节4.3生成的rpc gateway服务代码为例,一个共四个服务shopgw、product、inventory、comment,分别修改4个服务配置(在configs目录下),开启链路跟踪功能,并且填写jaeger配置信息。
在 product、inventory、comment 三个服务的internal/service目录下找到模板文件,填充代码替代panic("implement me")
,使得代码可以正常执行,并且手动添加一个span,添加随机延时。
启动 shopgw、product、inventory、comment 四个服务,在浏览器访问 http://localhost:8080/apis/swagger/index.html ,执行get请求,链路跟踪界面如图5-2所示。
图5-2 多服务链路跟踪页面
从图中可以看到共有10个span,主要链路:
shopgw服务串行调用了product、inventory、comment 三个服务获取数据,实际中可以改为并行调用会更节省时间,但是要注意控制协程数量。
采集指标用Prometheus,展示使用Grafana,在本地使用docker启动两个服务。
(1) prometheus服务
这是 prometheus服务启动脚本,启动prometheus服务:
docker-compose up -d
在浏览器访问prometheus主页 http://localhost:9090 。
(2) grafana服务
这是 grafana服务启动脚本,启动grafana服务:
docker-compose up -d
在浏览器访问 grafana 主页面 http://localhost:33000 ,设置prometheus的数据源 http://localhost:9090
,记住prometheus的数据源名称(这里是Prometheus),后面导入监控面板的json的datasource值要一致。
以章节3.1.2生成的http服务代码为例,默认提供指标接口 http://localhost:8080/metrics 。
(1) 在prometheus添加监控目标
打开prometheus配置文件 prometheus.yml,添加采集目标:
- job_name: 'http-edusys'
scrape_interval: 10s
static_configs:
- targets: ['localhost:8080']
注:如果使用vim修改 prometheus.yml 文件,修改前必须将文件 prometheus.yml 权限改为0777
,否则修改配置文件无法同步到容器中。
执行请求使prometheus配置生效 curl -X POST http://localhost:9090/-/reload
,稍等一会,然后在浏览器访问 http://localhost:9090/targets , 检查新添加的采集目标是否生效。
(2) 在grafana添加监控面板
把 http 监控面板 导入到grafana,如果监控界面没有数据显示,检查json里的数据源名称与grafana配置prometheus数据源名称是否一致。
(3) 压测接口,观察监控数据
使用wrk工具压测接口
# 接口1
wrk -t2 -c10 -d10s http://192.168.3.27:8080/api/v1/teacher/1
# 接口2
wrk -t2 -c10 -d10s http://192.168.3.27:8080/api/v1/course/1
监控界面如图5-3所示。
图5-3 http 服务监控界面
以章节4.1.1生成的rpc服务代码为例,默认提供指标接口 http://localhost:8283/metrics 。
(1) 在prometheus添加监控目标
打开prometheus配置文件 prometheus.yml,添加采集目标:
- job_name: 'rpc-server-edusys'
scrape_interval: 10s
static_configs:
- targets: ['localhost:8283']
执行请求使prometheus配置生效 curl -X POST http://localhost:9090/-/reload
,稍等一会,然后在浏览器访问 http://localhost:9090/targets 检查新添加的采集目标是否生效。
(2) 在grafana添加监控面板
把 rpc server 监控面板 导入到grafana,如果监控界面没有数据显示,检查json里的数据源名称与grafana配置prometheus数据源名称是否一致。
(3) 压测rpc方法,观察监控数据
使用Goland或VS Code打开internal/service/teacher_client_test.go
文件,对Test_teacherService_methods 或 Test_teacherService_benchmark 下各个方法进行测试。
图5-4 rpc server监控界面
上面是rpc服务端的监控,rpc的客户端的监控也类似,rpc client 监控面板 。
实际使用中服务数量比较多,手动添加监控目标到prometheus比较繁琐,也容易出错。prometheus支持使用consul的服务注册与发现进行动态配置,自动添加和移除监控目标。
在本地启动 consul 服务,这是 consul 服务启动脚本
打开 prometheus 配置 prometheus.yml,添加consul配置:
- job_name: 'consul-micro-exporter'
consul_sd_configs:
- server: 'localhost:8500'
services: []
relabel_configs:
- source_labels: [__meta_consul_tags]
regex: .*edusys.*
action: keep
- regex: __meta_consul_service_metadata_(.+)
action: labelmap
执行请求使prometheus配置生效 curl -X POST http://localhost:9090/-/reload
。
在prometheus配置好consul服务发现之后,接着把服务的地址信息推送到consul,推送信息 edusys_exporter.json 文件内容如下:
{
"ID": "edusys-exporter",
"Name": "edusys",
"Tags": [
"edusys-exporter"
],
"Address": "localhost",
"Port": 8283,
"Meta": {
"env": "dev",
"project": "edusys"
},
"EnableTagOverride": false,
"Check": {
"HTTP": "http://localhost:8283/metrics",
"Interval": "10s"
},
"Weights": {
"Passing": 10,
"Warning": 1
}
}
curl -XPUT --data @edusys_exporter.json http://localhost:8500/v1/agent/service/register
稍等一会,然后在浏览器打开 http://localhost:9090/targets 检查新添加的采集目标是否生效。然后关闭服务,稍等一会,检查是否自动移除采集目标。
对于自己的服务,通常启动服务时同时提交信息到consul,把 edusys_exporter.json 转为go struct,在程序内部调用http client提交给consul。
通常使用pprof工具来发现和定位程序问题,特别是线上go程序出现问题时可以自动把程序运行现场(profile)保存下来,再使用工具pprof分析定位问题。
sponge生成的服务支持 http接口 和 系统信号通知 两种方式采集profile,默认开启系统信号通知方式,实际使用一种即可。
通过http接口方式采集profile默认是关闭的,如果需要开启,修改配置里的字段enableHTTPProfile
为true,通常在开发或测试时使用,如果线上开启会有一点点性能损耗,根据实际情况是否开启使用。
默认路由 /debug/pprof
,结合go tool pprof工具,任意时刻都可以分析当前程序运行状况。
使用http接口方式,程序后台一直定时记录profile相关信息等,绝大多数时间都不会去读取这些profile,可以改进一下,只有需要的时候再开始采集profile,采集完后自动关闭,sponge生成的服务支持监听系统信号来开启和停止采集profile,默认使用了 SIGTRAP(5) 系统信号(建议改为SIGUSR1,windows环境不支持),发送信号给服务:
# 通过名称查看服务pid(第二列)
ps aux | grep 服务名称
# 发送信号给服务
kill -trap pid值
# kill -usr1 pid值
服务收到系统信号通知后,开始采集profile并保存到/tmp/服务名称_profile
目录,默认采集时长为60秒,60秒后自动停止采集profile,如果只想采集30秒,发送第一次信号开始采集,大概30秒后发送第二次信号表示停止采集profile,类似开关。默认采集cpu、memory、goroutine、block、mutex、threadcreate六种类型profile,文件格式日期时间_pid_服务名称_profile类型.out
,示例:
xxx221809_58546_edusys_cpu.out
xxx221809_58546_edusys_mem.out
xxx221809_58546_edusys_goroutine.out
xxx221809_58546_edusys_block.out
xxx221809_58546_edusys_mutex.out
xxx221809_58546_edusys_threadcreate.out
因为trace的profile文件相对比较大,因此默认没有采集,根据实际需要可以开启采集trace(服务启动时调用prof.EnableTrace())。
获得离线文件后,使用pprof工具使用交互式或界面方式进行分析:
# 交互式
go tool pprof [options] source
# 界面
go tool pprof -http=[host]:[port] [options] source
上面都是手动采集profile,通常都是希望出现问题时自动采集profile。sponge生成的服务默认支持自动采集profile,是结合资源统计的告警功能来实现的,告警条件:
触发告警时,程序内部调用kill函数发送系统信号通知采集profile,采集的profile文件保存到/tmp/服务名_profile
目录,其实就是在通过系统信号通知采集profile的基础上把手动触发改为自动触发,即使在半夜程序的cpu或内存过高,第二天也可以通过分析profile来发现程序哪里造成cpu或内存过高。
注:自动采集profile不适合windows环境。
sponge生成的服务默认支持Nacos配置中心,配置中心作用是对不同环境、不同服务的配置统一管理,有效的解决地静态配置的缺点。
在本地启动nacos服务,这是nacos服务启动配置,启动nacos服务之后,在浏览器打开管理界面 http://localhost:8848/nacos/index.html ,登录账号密码进入主界面。
以 章节3.1.2 生成的http服务代码为例使用配置中心nacos,在nacos界面创建一个名称空间edusys
,然后新建配置,Data ID值为edusys.yml
,Group值为dev
,配置内容值configs/edusys.yml
文件内容,如图5-3所示。
图5-3 nacos添加服务配置
打开edusys目录下配置中心文件configs/edusys_cc.yml
,填写nacos配置信息:
# Generate the go struct command: sponge config --server-dir=./serverDir
# nacos settings
nacos:
ipAddr: "192.168.3.37" # server address
port: 8848 # listening port
scheme: "http" # http or https
contextPath: "/nacos" # path
namespaceID: "ecfe0595-cae3-43a2-9e47-216dc92207f9" # namespace id
group: "dev" # group name: dev, prod, test
dataID: "edusys.yml" # config file id
format: "yaml" # configuration file type: json,yaml,toml
编译和启动edusys服务:
# 切换到main.go位置
cd cmd/edusys
# 编译
go build
# 运行
./edusys -enable-cc -c=../../configs/edusys_cc.yml
启动服务参数-c
表示指定配置文件,参数-enable-cc
表示从配置中心获取配置。
sponge创建的服务支持限流和熔断功能,默认是关闭的,打开服务配置文件,修改字段enableLimit值为true
表示开启限流功能,修改字段enableCircuitBreaker改为true
表示开启熔断功能。
限流和熔断使用第三方库 aegis,根据系统资源和错误率自适应调整,由于不同服务器的处理能力不一样,参数也不好设置,使用自适应参数避免每个服务去手动去设置参数麻烦。
sponge创建的服务支持在 jenkins 构建和部署,部署目标可以是docker、 k8s ,部署脚本在deployments目录下,下面以使用jenkins部署到k8s为示例。
为了可以在容器里编译go代码,需要构建一个 jenkins-go 镜像,这是已经构建好的 jenkins-go镜像。如果想自己构建 jenkins-go 镜像,可以参考docker构建脚本Dokerfile
准备好 jenkins-go 镜像之后,还需要准备一个k8s集群(网上有很多搭建k8s集群教程),k8s鉴权文件和命令行工具kubectl,确保在 jenkins-go 容器中有操作k8s的权限。
jenkins-go 启动脚本 docker-compose.yml 内容如下:
version: "3.7"
services:
jenkins-go:
image: zhufuyi/jenkins-go:2.37
restart: always
container_name: "jenkins-go"
ports:
- 38080:8080
#- 50000:50000
volumes:
- $PWD/jenkins-volume:/var/jenkins_home
# docker configuration
- /var/run/docker.sock:/var/run/docker.sock
- /usr/bin/docker:/usr/bin/docker
- /root/.docker/:/root/.docker/
# k8s api configuration directory, including config file
- /usr/local/bin/kubectl:/usr/local/bin/kubectl
- /root/.kube/:/root/.kube/
# go related tools
- /opt/go/bin/golangci-lint:/usr/local/bin/golangci-lint
启动jenkis-go服务:
docker-compose up -d
在浏览器访问 http://localhost:38080 ,第一次启动需要 admin 密钥(执行命令获取 docker exec jenkins-go cat /var/jenkins_home/secrets/initialAdminPassword
),然后安装推荐的插件和设置管理员账号密码,接着安装一些需要使用到的插件和一些自定义设置。
(1) 安装插件
# 中文插件
Locale
# 添加参数化构建插件
Extended Choice Parameter
# 添加git参数插件
Git Parameter
# 账号管理
Role-based Authorization Strategy
(2) 设置中文
点击【Manage Jenkins】->【Configure System】选项,找到【Locale】选项,输入【zh_CN】,勾选下面的选项,最后点击【应用】。
(3) 配置全局参数
dashboard --> 系统管理 --> 系统配置 --> 勾选环境变量
设置容器镜像的仓库地址:
# 开发环境镜像仓库
DEV_REGISTRY_HOST http://localhost:27070
# 测试环境镜像仓库
TEST_REGISTRY_HOST http://localhost:28080
# 生产环境镜像仓库
PROD_REGISTRY_HOST http://localhost:29090
创建jenkins新任务的一种相对简单的方法是在创建新任务时导入现有模板,然后修改git存储库地址,第一次使用jenkins还没有模板,可以按照下面步骤创建一个模板:
(1) 创建新的任务,如图6-1所示。
图6-1 创建任务界面
(2) 参数化构设置,使用参数名GIT_parameter
,如图6-2所示。
图6-2 设置参数化构建界面
(3) 设置流水线,如图6-3所示。
图6-3 设置流水线界面
(4) 构建项目
单击左侧菜单栏上的 Build with Parameters,然后选择要分支或tag,如图6-4所示。
图6-4 参数化构建界面
以章节3.1.2的edusys服务为例,使用jenkins构建和部署到k8s。
第一次构建服务需要做一些前期准备:
(1) 把edusys代码上传到代码仓库。
(2) 准备一个docker镜像仓库,确保jenkins-go所在docker有权限上传镜像到镜像仓库。
(3) 确保在k8s集群节点有权限从镜像拉取镜像,在已登录docker镜像仓库服务器上执行命令生成密钥。
kubectl create secret generic docker-auth-secret \
--from-file=.dockerconfigjson=/root/.docker/config.json \
--type=kubernetes.io/dockerconfigjson
(4) 在k8s创建edusys相关资源。
# 切换到目录
cd deployments/kubernetes
# 创建名称空间,名称对应spong创建服务参数project-name
kubectl apply -f ./*namespace.yml
# 创建configmap、service
kubectl apply -f ./*configmap.yml
kubectl apply -f ./*svc.yml
(5) 如果想使用钉钉通知查看构建部署结果,打开代码库下的 Jenkinsfile 文件,找到字段tel_num填写手机号码,找到access_token填写token值。
前期准备好之后,在jenkins界面创建一个新任务(名称edusys),使用上面创建的模板(名称sponge),然后修改git仓库,保存任务,开始参数化构建,构建结果如图6-5所示:
图6-5 jenkins构建结果界面
使用命令kubectl get all -n edusys
查看edusys服务在k8s运行状态:
NAME READY STATUS RESTARTS AGE
pod/edusys-dm-77b4bcccc5-8xt8v 1/1 Running 0 21m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/edusys-svc ClusterIP 10.108.31.220 8080/TCP 27m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/edusys-dm 1/1 1 1 21m
NAME DESIRED CURRENT READY AGE
replicaset.apps/edusys-dm-77b4bcccc5 1 1 1 21m
在本地测试是否可以访问
# 代理端口
kubectl port-forward --address=0.0.0.0 service/edusys-svc 8080:8080 -n edusys
# 请求
curl http://localhost:8080/api/v1/teacher/1
sponge生成的服务包括了Jenkinsfile、构建和上传镜像脚本、k8s部署脚本,基本不需要修改脚本就可以使用,也可以修改脚本适合自己场景。
如果觉得对你有用请给个star⭐,欢迎加入微信群交流。