公司的应用架构一开始就选定了微服务+Kubernetes,整个开发环境都在内网,使用 Jenkins 做半自动化的 CI/CD.
整个前后端都拆分得很细,分了很多层次:
最终只对外暴露出几个对外网关。
这种结构的目的,就是提升代码的复用能力,把应用层能复用的东西,都抽到下面两层去了。
但这要求我们的基础层API一开始就设计得足够好,因为越到后期,API 的影响面就越大,几乎无法修改。
目前我们是使用 Jenkins 作为 CI/CD 工具,层次结构也完全对应前面讲到的代码层次结构。
通过一套 BatchJob (批量构建任务,串行或并行地调用相关的子任务)来按依赖顺序,自下向上地层层更新 csharp/python/golang 依赖,构建 nuget 包,最后打包成 docker 镜像。
更新过程中会通过如下几个检测项来判断是否需要构建 nuget 包/docker 镜像:
每一个仓库的依赖更新与版本自增都对应一个 jenkins 任务,由每一层的 BatchJob 按预先定义好的顺序启动这些小任务。(相互独立的任务会被并行调用,以加速构建)
而在应用层,是通过 batchjob 并行构建所有的 docker 镜像。
有一个专门的 job_config 仓库(Python 模块),存储着:
构建完成后,需要通过一个“镜像快照”的功能,将所有镜像的版本号、扫描出来,然后生成它们的 k8s yaml 配置文件,保存到 git 仓库中,并打上 tag(时间戳)。方便随时回退。
k8s 配置生成方面,我们目前是使用的自定义模板,通过字符串替换的方式进行填充。以后可能会考虑使用 kustomize。
最后通过一个部署的任务将指定的版本的 yaml 应用到集群中。
另外现在正在考察 jenkins-x,以后可能会将应用层的镜像构建到 k8s 部署,从 jenkins 移出来。
3.1 Kubernetes 配置生成#
先说说 k8s 配置生成,试用了 kustomize,发现它功能还是比较弱,匹配不上我们现在的 yaml 配置生成的需求。也可能是我们目前的使用姿势不对吧。
3.2 GitOps#
另外也查了很多 Jenkins-X 的资料,它遵从 GitOps 开发流程,能检测 Git 仓库变更/Pull Request,直接生成 Docker 镜像并部署到 Preview - Stageing - Production 环境。
但是我 GitOps 和公司目前的这套构建体系不太契合:这种以 Git 仓库为中心的方式,好像只面向能生成最终的 Docker 镜像/K8s Pod 的 Git 代码,而不适合用于构建底层依赖包。
比如说我更新了一个基础层的依赖包 A,现在需要让所有的应用层项目都引用这个新版本依赖。应用层可能有几十上百个仓库,手动更新几乎不可能。
通过 GitOps 做不到让应用层的这上百个仓库自己更新一下底层依赖。只能借助一个 Jenkins 的 BatchJob,调用一下所有应用层依赖更新的子任务。
P.S. dotnet-nuget/java-maven 目前没有很多现代现代语言都有的 动态依赖(如
pyproject.toml
/package.json
/pubspec.yml
/go.mod
/Cargo.toml
,可以通过指定范围之类的方法灵活配置依赖) + 依赖快照(如pyproject.lock
/yarn.lock
/pubspec.lock
/go.sum
/Cargo.lock
,所有依赖的完整快照,保证当前依赖环境可复现) 这类的依赖管理方法,以及poetry update
/yarn upgrade
/flutter packages upgrade
/go get -u
/cargo update
之类的依赖升级命令。只有一个 xxx.csproj/xxx.xml 记录所有直接依赖的精确版本。要做自动化依赖管理,只能自己写脚本去访问 nuget api,读取并更新 csproj 文件。
查询资料 nuget versions 发现 nuget/maven 确实也支持基于范围的版本依管理,但问题是它们没有 xxx.lock 文件作为环境快照!这意味着使用灵活的依赖管理,可能导致历史环境无法复现。
另外 GitOps 自动化部署的流程也和公司目前的部署方法不切合。我们每个开发/测试人员都有一套自己的开发/测试环境,有的测试会需要使用特定版本的一套后端微服务。也就是说测试人员需要能够控制自己测试环境整套微服务的版本,比如回退到某个时间点、将版本固定在某个时间点。而且不能在工作时间自动更新测试环境的后端微服务。(否则测试到一半,后端滚动更新了微服务,那大半天的测试就作废了。)
GitOps 只适合一些扁平的应用,而对公司这种分层结构的代码就有点无所适从。
解决方法
旧的分层更新任务会修改应用层的 csproj 文件,这样就会触发 GitOps。
以这种方式进行结合是比较好的,GitOps 和现有的分层结构不会冲突。
3.3 网络问题/缓存问题#
每次构建 dotnet 程序时,都需要从公网拉取依赖,使用静态 Jenkins Slave 时,拉了第一遍后本地就有缓存了,不需要再拉第二第三遍。
可用 Serverless Jenkins (Jenkins-X)的话,每次都是启动新容器,岂不是每次都要拉依赖?这个挺费时间的。
暂时想到的解决方案是使用 Baget 的缓存功能,让私有 nuget 仓库来缓存这些依赖。
开发人员需要一个直观的 UI 界面,进行生产环境的灰度部署、监控分析。我们调研了很多管理平台:Rancher/Rainbond/KubeSphere,以及这些工具与 OAM/Istio 的契合度。
目前想到的比较好的一个方案,就是 flux+flagger+istio(istio 可换成 linkerd2),
此方案使用 github.com/gitee.com/coding.net 私有仓库保存生产环境的 k8s 配置,在内网通过 jenkins 生成 k8s 配置,在生产环境中通过 flux 监控该仓库的更新,然后 flux 使用 flagger 对其中的 k8s deployment 变更进行自动的灰度发布。
另外 Jenkins-X 有 Preview-Staging-Production 的一套 GitOps 部署流程,也可以一试。只是 jx 目前只提供了 CLI,没有 UI。上手可能有点难。