小团队的 微服务 + CI/CD + Kubernetes 实践

公司的应用架构一开始就选定了微服务+Kubernetes,整个开发环境都在内网,使用 Jenkins 做半自动化的 CI/CD.

整个前后端都拆分得很细,分了很多层次:

  1. 脚手架层:封装了开源的各种库(mysql/redis/es 的 CRUD 库,log/tracing/config 库等)。
  2. 基础层(BaseXxx):基于脚手架层,实现了如下几类应用的基础层:测试器、数据库升级器、微服务、网关、通用工具、一次性任务等。
  3. 中台:在基础服务上,按功能实现了一些比较通用的中台服务。比如权限认证服务、用户服务、订单服务等。
  4. 应用层:每个仓库对应一个具体的微服务,也可能包含测试器、数据库升级器等。可能会在 CI 中被打包成多个镜像。

最终只对外暴露出几个对外网关。

这种结构的目的,就是提升代码的复用能力,把应用层能复用的东西,都抽到下面两层去了。
但这要求我们的基础层API一开始就设计得足够好,因为越到后期,API 的影响面就越大,几乎无法修改。

一、CI/CD#

1. Continuous Integration 方案#

目前我们是使用 Jenkins 作为 CI/CD 工具,层次结构也完全对应前面讲到的代码层次结构。

通过一套 BatchJob (批量构建任务,串行或并行地调用相关的子任务)来按依赖顺序,自下向上地层层更新 csharp/python/golang 依赖,构建 nuget 包,最后打包成 docker 镜像。

更新过程中会通过如下几个检测项来判断是否需要构建 nuget 包/docker 镜像:

  1. 更新私有依赖,返回值:是否更新了依赖
  2. 更新第三方依赖的小版本,返回值:是否更新了依赖、是否有主版本变更(主版本需要手动升级)
  3. 与上次构建相比,代码仓库是否存在变更(目的是加速构建)

每一个仓库的依赖更新与版本自增都对应一个 jenkins 任务,由每一层的 BatchJob 按预先定义好的顺序启动这些小任务。(相互独立的任务会被并行调用,以加速构建)

而在应用层,是通过 batchjob 并行构建所有的 docker 镜像。

有一个专门的 job_config 仓库(Python 模块),存储着:

  1. Git 仓库、Jenkinsfile 与 Jenkins 任务的对应关系,以 yaml 格式保存
    • 通过 python 代码提供 api,动态地从上述配置中查询出各层的 Git 仓库、Jenkins 任务的各种信息。
    • 提供命令从上述 yaml 配置中生成出所有的 jenkins jobs 配置(xml文档),这样就不需要通过 UI 一个个添加 Jenkins 任务。
  2. 上述的任务分层、任务之间的优先级(相同优先级的任务是相互独立的,可并行构建)

2. Continuous Deployment 方案#

构建完成后,需要通过一个“镜像快照”的功能,将所有镜像的版本号、扫描出来,然后生成它们的 k8s yaml 配置文件,保存到 git 仓库中,并打上 tag(时间戳)。方便随时回退。

k8s 配置生成方面,我们目前是使用的自定义模板,通过字符串替换的方式进行填充。以后可能会考虑使用 kustomize。

最后通过一个部署的任务将指定的版本的 yaml 应用到集群中。

另外现在正在考察 jenkins-x,以后可能会将应用层的镜像构建到 k8s 部署,从 jenkins 移出来。

3. CI/CD 目前存在的问题#

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 只适合一些扁平的应用,而对公司这种分层结构的代码就有点无所适从。

解决方法

  1. 已有的 Jenkins 分层更新构建流程不变,只在应用层进行 GitOps 方式的 CI/CD。因为应用层仓库相互之间是独立的。
  2. 给 GitOps 的构建部署提供专用环境:Preview - Stageing - Production,个人服务器的部署仍然使用现有流程。

旧的分层更新任务会修改应用层的 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。上手可能有点难。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值