前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Gitlab CI/CD 实践六:统一管理 protocol buffer,API 大仓设计与实现

Gitlab CI/CD 实践六:统一管理 protocol buffer,API 大仓设计与实现

作者头像
Yuyy
发布2022-09-08 15:43:18
1.4K0
发布2022-09-08 15:43:18
举报
文章被收录于专栏:yuyy.info技术专栏

背景

目前公司采用 protocol buffer 作为 IDL,虽然可以根据 API 定义,轻松生成客户端和服务端的代码。但是对于跨项目的接口,会增加项目之间的耦合性。例如A服务对外提供了一个接口,B服务去调用。那么就需要根据A服务的proto文件,生成客户端代码,并拷贝给B。如果联调期间,A服务改动了该接口,还需重复前面的步骤,非常繁琐。

由此引出两个问题,proto文件放在哪合适?调用方如何获取生成的接口客户端代码?

如何解决

常见的几种解决方案,煎鱼大佬已经描述得很详细了(真是头疼,Proto 代码到底放哪里?),这里不再赘述。

经过查阅资料,总结出适用于我们项目的几种方案。

方案一:api大仓+git submodule(b站)

  • Proto文件只有一份,没有拷贝。
  • pr和发布解耦,修改api后,不用完成pr,他人切换到对应分支,就能使用。
存在的问题
  • build时需要将整个api 大仓都生成中间代码。
    • java项目可通过maven指定部分api文件。
    • go项目需要新增yaml文件描述当前项目依赖哪些pb文件,再通过脚本去生成中间代码。
  • 维护 Makefile,使用 protoc + go build 统一处理。
    • 脚本难写。
    • 每个项目都得维护相同功能的Makefile。重复代码,想修改、优化脚本就很难。新项目新同事不清楚参考哪个老项目来写脚本,可能抄了一个存在已发现缺陷但当前项目未修改的老版本脚本。
    • 和Gitlab CI\CD流水线脚本一个道理,最终不得不抽取公共脚本到一个专属仓库,其他项目采用引入的形式来做。但是非流水线脚本,没有引入操作。
    • 个人项目、单一项目可采用这种方案,企业级的就得写复杂脚本了。

方案二:api大仓+git submodule + 每个项目生成代码专有仓库

  • 生成代码交给ci。
  • 使用时通过go依赖引入,无需编写生成代码的脚本。
  • 依赖服务A的接口,只需go get服务A的接口文件生成的代码。
存在的问题
  • 每个go项目都要去创建一个存放跟进api定义生成的代码的仓库

方案三:每个项目都有一个api仓库,包含生成的代码

  • 和方案二类似,只是把api大仓拆了。
存在的问题
  • 和方案二一样。
  • api代码提pr时,会展示api生成的代码,非源码,影响 CodeReview。
  • api文件分散,不好集中管理、查看。特别是企业里,还得给新人配置多个 api 仓库的权限。

方案四:api大仓 + api生成代码的集中仓库

  • 将方案二里的每个项目都创建一个api生成代码的仓库,改成一个整合的大仓库。
  • 使用时go get依赖一个大仓库即可
存在的问题
  • 依赖服务A的接口,需要通过go get引入所有服务的接口文件生成的代码
    • 不过这个问题不严重
      1. 这个仓库体积不大,因为接口定义文件,整个公司也没多少,一个项目才几个文本文件,生成的代码也不多。
      2. Java不同,go build不会将依赖包全部构建到二进制文件里,只会构建项目里实际用到的文件。

权衡了下,最终选择方案四。

具体实现

API 大仓:xxxapis

这里主要的工作就是 API 大仓的CI脚本.gitlab-ci.yml
代码语言:javascript
复制
stages:
  - lint
  - generate

variables:
  BUF_CACHE_DIR: /cache/${CI_PROJECT_PATH}/buf-cache
before_script:
  - mkdir -p $BUF_CACHE_DIR

buf_lint:
  stage: lint
  image: 172.x.x.x/common/buf:1.6.0
  tags:
    - 172.x.x.x-runner
  interruptible: true
  script:
    - buf mod update
    - buf lint --error-format=json --timeout 5m

generate_go_file:
  stage: generate
  image: 172.x.x.x/common/buf:1.6.0
  tags:
    - 172.x.x.x-runner
  interruptible: true
  variables:
    TARGET_REPOSITORY_ADDR: git@xxx.com:xxxapis/xxx-api-go.git
    TARGET_REPOSITORY: xxx-api-go
  script:
    - chmod +x ./script/generate-go-file-to-xxx-api-go.sh
    - ./script/generate-go-file-to-xxx-api-go.sh
generate-go-file-to-xxx-api-go.sh
代码语言:javascript
复制
#!/bin/bash

echo "-------------------- 根据 proto 文件生成go代码 --------------------";
buf mod update;
buf generate --timeout 5m;

echo "-------------------- 同步 proto 文件生成的go代码到 ${TARGET_REPOSITORY} 仓库 --------------------";

echo "-------------------- 配置 git ssh,实现免密提交到 ${TARGET_REPOSITORY} 仓库 --------------------";
eval $(ssh-agent -s)
ssh-add <(echo "$CI_AUTO_SYNC_SSH_PRIVATE_KEY");
mkdir -p ~/.ssh;
touch ~/.ssh/config;
echo "StrictHostKeyChecking no" >> ~/.ssh/config;
echo "UserKnownHostsFile /dev/null" >> ~/.ssh/config;

echo "-------------------- 配置 git 用户信息为当前触发流水线的用户 --------------------";
git config --global user.email "$GITLAB_USER_EMAIL";
git config --global user.name "$GITLAB_USER_LOGIN";

echo "-------------------- git clone ${TARGET_REPOSITORY} 仓库,只拉取指定分支的最后一次 commit --------------------";
cd /tmp;
BRANCH=$CI_COMMIT_BRANCH;
if ! (git clone --depth 1 --branch $BRANCH $TARGET_REPOSITORY_ADDR); then
  echo "新建分支:$BRANCH"
  git clone --depth 1 --branch main $TARGET_REPOSITORY_ADDR;
  cd ${TARGET_REPOSITORY};
  git checkout -b $BRANCH;
fi

echo "-------------------- 拷贝go文件到 ${TARGET_REPOSITORY} 仓库 --------------------";
rm -rf /tmp/${TARGET_REPOSITORY}/xxx
mv $CI_PROJECT_DIR/apigen/xxx /tmp/${TARGET_REPOSITORY};
cd /tmp/${TARGET_REPOSITORY}
go mod tidy;

echo "-------------------- 提交到 ${TARGET_REPOSITORY} 仓库 --------------------";
cd /tmp/$TARGET_REPOSITORY;
git add .;
git commit -m "sync: 通过 ${CI_PROJECT_PATH} gitlab ci 同步 proto 文件生成的go代码" || true;
git push --set-upstream origin $BRANCH;
echo "-------------------- 同步成功 --------------------";
  • CI_AUTO_SYNC_SSH_PRIVATE_KEY:在gitlab配置的变量,具体谷歌gitlab配置ssh
buf配置
buf.yaml
代码语言:javascript
复制
# 配置模块信息,包括依赖项
version: v1
deps:
  - buf.build/googleapis/googleapis
lint:
  use:
    - DEFAULT
  except:
    - FIELD_LOWER_SNAKE_CASE
    - RPC_REQUEST_STANDARD_NAME
    - RPC_RESPONSE_STANDARD_NAME
    - RPC_REQUEST_RESPONSE_UNIQUE
breaking:
  use:
    - FILE
buf.gen.yaml
代码语言:javascript
复制
# 配置protoc生成规则
version: v1
managed:
  enabled: true
  go_package_prefix:
    default: xxx.com/xxxapis/xxx-api-go
    except:
      - buf.build/googleapis/googleapis

plugins:
  - name: go
    out: apigen
    opt: paths=source_relative
  - name: go-grpc
    out: apigen
    opt:
      - paths=source_relative
      - require_unimplemented_servers=false
  - name: grpc-gateway
    out: apigen
    opt: paths=source_relative
  - name: openapiv2
    out: apigen
    opt:
      - allow_repeated_fields_in_body=true

API-GO 仓库:xxx-api-go

只是存放通过xxxapis仓库ci同步过来的文件。

go.mod
代码语言:javascript
复制
module xxx.com/xxxapis/xxx-api-go

go 1.17

require (
   github.com/grpc-ecosystem/grpc-gateway/v2 v2.10.3
   google.golang.org/genproto v0.0.0-20220707150051-590a5ac7bee1
   google.golang.org/grpc v1.47.0
   google.golang.org/protobuf v1.28.0
)

require (
   github.com/golang/protobuf v1.5.2 // indirect
   golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
   golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect
   golang.org/x/text v0.3.7 // indirect
)

参照google,这两个仓库放到gitlab的xxxapis组下。

如何使用

这里就直接贴上xxxapis项目的readme。

xxxapis

公司所有 API 定义文件(protocol buffer)统一存放到此仓库。

一、如何使用
1. 其他项目如何通过git submodule的方式引入 API 大仓?
代码语言:javascript
复制
git submodule add https://xxx.com/xxxapis/xxxapis.git
  • 提交代码时,需要提交.gitmodules文件和xxxapis文件夹。

每个项目都引入 API 大仓,会不会浪费空间?

API大仓体积很小的,一个项目的接口定义就几个文本文件。

2. 如何下载git submodule的代码?
3. 如何更新、提交git submodule的代码?

进入子仓目录,和正常的仓库一样,运行git pullgit submit,切记要检查当前所在分支是不是游离的。

4. 提交proto文件到API大仓后,如何使用根据proto文件生成的客户端、服务端代码?

go

提交proto文件后,会通过流水线生成对应的go代码,并上传到xxx-api-go。时间目前测试为半分钟,流水线跑完会有邮件提醒。

代码语言:javascript
复制
go get xxx.com/xxxapis/xxx-api-go@main
  • 如果只是提交到feature分支,还未合并到main,上诉命令需要修改末尾的分支名。
  • 跨项目联调时,可使用相同的分支。
  • 依赖包里还有 swagger 接口文档

java

可使用maven插件,具体请参考maven + protobuf + gRPC + gitlab CI

其他语言

暂未考虑,需要时再扩展吧。

二、项目结构

存放 proto文件的目录:

  • 一级目录:公司名称
  • 二级目录:项目所在gitlab里的组
  • 三级目录:项目所在gitlab里的项目名
  • 四级目录:如果该项目只有一个服务,四级目录为接口版本号。如果项目包含多个服务,四级目录为服务名。
三、分支管理

此项目采用Github Flow,持续发布。只有一个长期分支:main,新功能基于main打feature分支,格式为feature/xxx功能,不用带版本号,因为此项目目前没有使用版本号管理,接口版本通过目录来体现。最后提合并请求到main分支,成功合并后就代表发布了。

参考

git submodule使用方法

参考资料

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-7-11 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 如何解决
    • 方案一:api大仓+git submodule(b站)
      • 存在的问题
    • 方案二:api大仓+git submodule + 每个项目生成代码专有仓库
      • 存在的问题
    • 方案三:每个项目都有一个api仓库,包含生成的代码
      • 存在的问题
    • 方案四:api大仓 + api生成代码的集中仓库
      • 存在的问题
  • 具体实现
    • API 大仓:xxxapis
      • 这里主要的工作就是 API 大仓的CI脚本.gitlab-ci.yml
      • generate-go-file-to-xxx-api-go.sh
      • buf配置
    • API-GO 仓库:xxx-api-go
      • go.mod
      • xxxapis
      • 二、项目结构
      • 三、分支管理
      • 参考
  • 如何使用
  • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档