目录
一、前言
二、架构演进过程
1. 本地单体模式
2. 云产品主备模式
2.1 上云的架构图
2.2 上云的过程
2.3 未考虑到的一些问题
2.4 需要改进的方向
3. 多区多活集群模式
3.1 Cluster集群架构
3.2 Cluster集群部署
3.3 存储架构切换
3.4 存量数据迁移
3.5 服务架构切换
三、治理经验
四、后续规划
本地单体模式
-
虚拟机模式:分配的CPU、内存可能不足导致GitLab访问异常的问题 -
物理机模式:受限于本地机房的保障措施,例如电源插头、网络保障、电力保障、硬件生命周期等
-
代码都存储在本地机房安全可靠 -
本地网络环境内的访问速度较高
-
本地机房稳定性保障不够,面临着断电、断网问题 -
受限于硬件现有的配置,机器升级流程慢(从评估采购合理性、采购流程、新机器采购、部署到机房、迁移数据等,至少得一两个月的时间) -
故障恢复时间长,例如机房服务器宕机、硬件坏掉这些风险点一旦发生,将是灾难性的故障
-
机器硬件升配扩容方便,不再依赖漫长的采购流程 -
数据库、存储的备份依赖云产品的快照备份能力 -
云产品的SLA高于本地机房
云产品主备模式
-
GitLab自身是有状态的服务,未拆分组件的情况下直接启动多个Pod会导致并发写的问题 -
在高峰期CPU和内存的消耗很高,容器模式下单个pod需要分配较高的CPU、内存,但性能却比不上物理机 -
部署在k8s集群后,不仅要踩坑部署后的各种问题处理及优化,另一方面需要熟悉k8s集群的各种维护事项,日常维护成本大大增加
上云的架构图
-
postgresql、redis拆分为单独的外部实例,实例自身高可用 -
存储改为nas盘,每小时进行快照备份 -
主备两台服务器安装GitLab服务,备节点默认情况下不启动 -
每天晚上完整的备份一次数据到备节点
上云的过程
-
第一步:提前一周部署好云上的GitLab环境,域名访问通过绑host,此时是一个空的环境 -
第二步:将本地数据进行全量备份,还原到云上的GitLab环境,此步骤历经25个小时,这一步我们有了一个基于备份的全量数据的环境,数据版本停留在备份的时刻,后面需要解决增量的问题 -
第三步:每天晚上本地代码文件增量同步到云上的nas盘内,光这一步还不够,因为数据库的数据没有同步 -
第四步:简化备份脚本,只备份数据库、redis缓存,在1.5小时内即可得到数据的完整备份 -
第五步:串联文件同步脚本+数据备份还原脚本,执行同步还原,同时执行数据的备份还原以及增量文件的同步,将最新的备份上传至云上环境,降低增量的数据量,经过多天的测试验证,一天的增量数据从本地机房迁移到云上4个小时左右即可完成,验证操作过程形成SOP -
第六步:执行正式的数据迁移,旧的环境开启只读模式,避免迁移过程的增量数据丢失问题;若有紧急需求临时开启单个仓库的代码提交通道,后续将该仓库重新推送一次代码即可; -
第七步:数据迁移完后切换域名解析即生效
未考虑到的一些问题
-
本地机房ios打包机全量拉代码打包慢、部分webhook地址不通,ios打包慢的问题通过打包机缓存+带宽定向调优解决 -
webhook不通是由于要访问的地址有公网解析、内网解析、办公网解析各种情况,这类问题通过修改网络策略以及解析方式解决
需要改进的方向
-
主备模式严格意义上来说并非较好的高可用架构方案,需要人工介入切换 -
nas盘并不支持多区可用的特性,nas盘也存在出问题的风险点 -
由于rails组件和gitaly组件部署在一起,这么多的仓库的文件操作都在一台服务器上,导致主服务器的峰值压力过大,在一些特定场景下会导致CPU峰值过高,如下图所示:
多区多活集群模式备模式
Cluster集群架构
-
Rails:提供web服务,例如GitLab页面的的访问就上由该组件提供服务的,另外在每一次请求中还需要经过它来做身份认证,该组件服务消耗内存 -
Praetect:负责将Rails节点接受到文件操作的请求的转到Gitaly节点,充当一个桥梁作用,Praetect负责多Gitaly节点的数据一致性保障,该组件服务只需要较低的资源 -
Gitaly:负责与磁盘上的代码文件进行交互,例如Clone/pull代码、新增的代码更新、新增分支、新增tag都由Gitaly负责读取或写入存储盘,该服务大量操作磁盘,对CPU和磁盘的吞吐要求较高
-
GitLab服务拆分为Rails、Praetect、Gitaly三个组件,每个组件分别部署3台服务器(Praetect、Gitaly部署在相同节点上),分布在两个可用区 -
Rails节点用于提供web服务,上层通过SLB为用户提供统一访问入口 -
Rails节点与Gitaly的节点通讯采用SLB到Praetect,由Praetect进行访问Gitaly,Praetect先将数据写入Gitaly主节点,再将数据复制到Gitaly辅节点来保证三副本数据最终一致性 -
代码文件存储从nas盘改为SSD存储,降低网络存储的延时
Cluster集群部署
-
SLB分别用于访问rails节点为外部提供服务以及rails访问Praetect节点 -
Rails节点、Praetect和Gitaly节点,需要分布在不同的可用区,可用区之间的延时越低越好 -
SSD存储盘,挂载到Gitaly节点服务器 -
PG数据库实例、Redis在Rails节点、Praetect都会使用到 -
nas盘挂载到Rails节点,nas盘在这里主要的作用是用于gitlab-ci任务的制品共享,该数据并非核心存储,若遇到极端的nas挂的情况下可以将Rails改成单节点本地存储提供服务
-
域名、证书等基础配置的参数修改 -
PG数据库、Redis的链接配置参数,Rails节点与Praetect是使用不同的库(为避免直接影响生产环境,这里的数据库和Redis均采用临时的实例,与生产环境数据隔离) -
SLB上的节点端口配置,Rails节点相关的端口,Praetect节点相关的端口,确保网络策略放行这些端口
-
关闭默认开启的Gitaly、Praetect组件功能以及其他不需要的组件 -
git_data_dirs指定存储池,需要指定两个存储池default和gitaly_cluster,官方文档里提到default是必需的,gitaly_address需要指定为为Praetect节点准备的SLB地址,另外基于安全考虑,在通信过程中需要增加Token认证,这个认证的Token多处使用到,需要保持一致
-
修改gitlab_workhorse监听地址为tcp模式,默认是unix,监听端口需要与后面的gitaly访问的端口保持一致 -
修改指定的gitaly访问的监听配置端口为后面gitaly节点上监听的端口 -
修改gitlab_rails[‘shared_path’]参数为nas盘的挂载的路径
-
关闭默认开启的Rails组件功能以及其他不需要的组件 -
gitlab-secrets.json配置需要与Rails节点上的该文件保持一致,reconfigure后手动复制覆盖即可 -
修改Praetect、Gitaly组件的Token信息,注意与Rails节点里配置保持一致
上述配置信息并不完整,仅列出几个关键的配置点,例如还有prometheus、grafana、Sidekiq参数均需要根据实际情况来调整,每一个节点都需要根据节点类型来进行修改。
存储架构切换
-
修改上述搭建好的Cluster集群里的Gitaly节点上的数据库配置为主备模式服务生产环境所用的配置 -
主备模式服务生产主节点里的git_data_dirs的default为默认存储不变,gitaly_cluster存储改为新的存储池SLB地址 -
gitlab_workhorse、gitaly监听地址、认证Token修改方式与Rails节点一致 -
拷贝主备模式下的gitlab-secrets.json文件到各个Gitaly节点机器
存量数据迁移
gitlab-rails console
# 检查配置项,默认是false
Feature.enabled?(:gitaly_replicate_repository_direct_fetch)
# 开启
Feature.enable(:gitaly_replicate_repository_direct_fetch)
-
支持批量迁移用户的snippets代码片段 -
支持批量迁移多个仓库代码 -
支持按指定的仓库进行迁移 -
支持按Group进行迁移Group下所有的仓库 -
支持查询迁移的状态(部分仓库确实存在失败的情况,例如脏数据引起的readonly)
-
第一批:迁移自己部门所在的仓库,大概数十个仓库左右,确保至少3天使用下来无问题,即使有问题也影响的是自己部门的 -
第二批:3天后迁移内部支撑所有部门的仓库,大概20%的仓库,观察3天(这个时候若出问题仅影响内部系统,并不会影响业务的服务,影响范围相对可控) -
第三批:这一批选择的是仓库体积不太大的业务部门,因为仓库体积小迁移时间快,以部门的方式分多个小批次进行操作迁移,小批次迁移分了3天完成,到此完成了60%左右 -
第四批:最后一批主要是体积较大的一些仓库,例如算法、App组件仓库,大于2G的仓库较多,这里采用了比较稳妥的策略,按仓库体积进行排序,将Top20的仓库单独放一批进行迁移,其余的让脚本依次执行
服务架构切换
-
修改Cluster集群Rails节点里的数据库、Redis链接配置为生产环境实例 -
将原有生产环境机器上的文件/etc/ssh/ssh_host*覆盖至Rails节点的三台服务器 -
拷贝主备模式下的gitlab-secrets.json文件到各个Redis节点机器 -
执行reconfigure命令,确保修改的配置生效,另外咱们内部还有一些二开的功能需要额外修改nginx配置让其生效 -
启动服务后,检查各项基础功能是否正常,如出现报错则需要查看日志来排查原因 -
最后要确保authorized_keys认证走数据库逻辑,否则就会出现新添加的公钥仅在某个节点生效导致66.6%的概率SSH认证不通过,若原来走的文件认证,需要在管理后台修改,如下图:
-
system/web hook治理:造成该现象的原因是因为大量的hook事件需要处理(包含非常多的失效接口,超时10s),默认的sidekiq线程数处理不过来,解决方法为一方面清理无效hook地址、系统钩子收敛hook转发到kafka后由各自消费kafka,另一个是增加sidekiq线程数数量,如下:
# 该值不宜太大,太大会导致数据库连接数过大
sidekiq['queue_groups'] = ['*'] * 8
-
API调用治理:GitLab默认仅支持账号级别限流,不支持接口级别限流,部分接口的QPS上限并不高,另外也无法看到每个账号的实际调用情况,这里我们采取的做法是实现了一个GitLab API接口网关服务,对于新增的API调用与高频调用的系统要求必须接入我们的网关服务才能使用(接入成本低) -
元数据使用治理:较多的内部平台都需要获取仓库列表、分支、成员等信息,我们仓库数量多,如果每个系统都直接去调GitLab,这个请求量也非常大耗时久而且还需要分页,为了解决这个问题,我们基于原生API+system hook扩展出元数据代理服务,通过独立的redis缓存这些数据,为各个平台提供更简单的API,一方面降低了请求压力,另一方面让调用方更方便的获取到需要的元数据 -
Clone代码治理:在高频Clone代码的使用场景,需要指定depth参数为1,仅Clone最新的一个版本,避免全量Clone带来的磁盘IO压力 -
错误码治理:非200的很多请求都是无效请求,若请求过多也会对服务产生压力,这部分请求可以通过调用IP以及账号联系相关使用方进行改进优化 -
变更规范治理:GitLab服务所有的变更都有可能影响服务的稳定性,无论是运维侧、安全侧还是我们自己的日常变更,都应遵守变更规范,只允许低峰进行,并且所有的变更都需要经过系统Owner的同意! -
使用行为监测:要做到提前发现风险,并且在风险发生时能快速定位到问题原因,我们结合治理过程的经验,做了账号调用行为监测机制,例如:API调用告警、账号Clone代码量等
-
故障演练:持续的通过故障演练验证监控告警的有效性以及各项故障恢复SOP流程 -
完善巡检工具:完善风险治理监测巡检工具,例如数据库风险、API调用风险、Clone代码风险,提前感知风险,提升问题定位效率 -
版本升级探索:实现外部网关功能支持SSH/Https协议,通过网关让新老版本并存,分批迁移仓库数据达到版本升级的目的,这种方式对研发同学的使用无感
往期回顾
*文 / RongChang
关注得物技术,每周一、三、五更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
“
扫码添加小助手微信
如有任何疑问,或想要了解更多技术资讯,请添加小助手微信:
本篇文章来源于微信公众号:得物技术
本文来自投稿,不代表TakinTalks稳定性技术交流平台立场,如若转载,请联系原作者。