2020 年 5 月,我们与 OnGres 合作,对 GitLab 上的 Postgres 集群进行版本大更新,从 9.6 版本升级到 11 版本。升级全部在维护窗口内运行,没有丝毫差错;更新中所有涉及的内容、计划、测试,以及全流程自动化,全部进行拆包,只为实现一次近乎完美的 PostgreSQL 升级。
本次版本更新,我们面临的最大难题在于如何利用一个规划完善的 pg_upgrade,方便且高效地对整体项目进行重要版本升级。为此,我们需要制定一个回滚计划,以保证 12 节点集群的 6 TB 数据一致的同时,优化恢复目标时间(RTO)后的容量,为 600 万用户提供每秒 300000 次的聚合交易服务。
解决工程难题的最佳方案是按照蓝图和设计文档行事。在创建蓝图的过程中,我们需要定义目标问题,评估最合适的解决方案,并考虑每个解决方案的优缺点。
在此,我们附上为这个项目准备的蓝图链接。
我们决定在 GitLab 13.0 中停止对 PostgreSQL 10.0 的支持,而 PostgreSQL 9.6 版本将在 2021 年 11 月 EOL(项目终止),因此,我们需要采取相应的行动。
以下是 PostgreSQL9.6 和 11 版本之间的主要区别:
PostgreSQL 集群的基础架构容量由 12 个服务于 OLTP 以及异步管道的 n1-highmem-96 GCP 示例组成,同时还有两个不同规格的 BI 节点,每个节点都有 96 个 CPU 内核以及 614GB 的 RAM。HA 集群通过 Patroni 进行管理和配置,以保证 Consul 集群及其所有复制体在异步流复制中,使用复制槽和 WAL 对 GCS 存储桶进行复制工作时的 leader 选举一致性。
我们的配置目前使用的是 Patroni HA 解决方案,它会不断收集集群、leader 检测,以及节点可用性的关键信息。该解决方案采用 Consul 的 DNS 服务等关键功能来实现,进而更新 PgBouncer 端点,确保读写和只读流量使用不同架构。
GitLab.com 架构
因为 HA 的缘故,其中两个复制体不在只读服务器列表池中,而是由 Consul DNS 支持,服务于 API。对 GitLab 架构的几次改进后,我们得以将项目整体降到 7 个节点。
此外,我们的整个集群平均每周要处理大约 181000 个交易每秒,如下图所示,流量会在周一有明显增加,并在周一至周五 / 六内保持该吞吐量。我们需要让维护影响到尽量少的用户,因此流量数据的统计对于设置合适的维护窗口至关重要。
GitLab.com 上连接数量统计
项目整体在全天中最忙碌的时刻可以到达 25000 交易每秒。
GitLab.com 上 commit 数量统计
与此同时,项目处理的交易峰值可以到达每秒 30 万次交易,GitLab.com 能到达每秒 6 万次连接。
在生产环境进行升级前,我们首先确定了一些需求:
为使生产升级能顺利运行,我们将项目划分为以下几个阶段:
第一阶段:在封闭环境中开发自动化
第二阶段:在 staging 中将升级开发与配置管理进行分段式融合
第三阶段:在 staging 上测试端到端升级
我们总共在 staging 中运行过 7 次测试,并通过反馈不断完善程序。
第四阶段:升级进入生产环境
生产环境的步骤与 staging 中类似,我们计划迁移八个节点,留下四个作为备份。
回滚计划只会在数据库不一致或者 QA 测试出错时才调用,以下是具体步骤:
升级中的所有步骤都在用于运行项目的模板中有详细说明
pg_upgrade 让我们可以在不用 dump/reload 策略,不用更多停机时间的情况下,将 PostgreSQL 数据文件升级到日后的主要版本。
正如在 PostgreSQL 官方文档中所写,pg_upgrade 工具通过避免执行 dump/restore 的方法来升级 PostgreSQL 版本。这里有几点细节需要注意:PostgreSQL 的主要版本会添加新功能,这些新功能经常会改变系统表的布局,但内部数据存储格式基本会保持不变。如果某次主要版本升级改变了数据格式,那么就不能继续用 pg_upgrade 了。因此,我们必须要先验证这些版本之间都有什么变化。
还有一点很重要,任何外部模块都必须兼容二进制,虽然你并不能通过 pg_upgrade 来检查这点。对 GitLab 的更新来说,我们在升级前先卸载了 postgres_exporter 等视图及拓展,以便在升级后重新创建,出于兼容性考虑,还要稍作修改。
在更新之前,必须先安装新版本的二进制文件。新的 PostgreSQL 二进制文件及拓展文件都装在需要升级的主机中。
pg_upgrade 在使用时有很多选项。我们选择在 Leader 节点上使用 pg_upgrade 的链接模式,因为维护窗口很短暂,只有两个小时。这种模式可以通过 inode 硬链接文件,避免了复制 6TB 文件的麻烦。缺点则是旧数据集群无法回滚到 9.6 版本。我们保存了 9.6 版本的副本和 GCP 快照作为后备计划的回滚路径。因为从头开始重建副本是不可能,所以我们选择使用 rsync 增量功能来进行升级。pg_upgrade 的官方文档也有写:“从主服务器上位于旧数据库集群目录和新数据库集群目录上方的目录中,在每个备用服务器的 primary 上运行此命令。”
ansible-playbook 对于这一步的实现,是通过从 leader 节点到每一个副本都有一个任务,在新旧数据目录中的父目录中触发 rsync 命令。
任何的迁移或数据库升级都需要在最终的生产升级前进行回归测试。对团队来说,数据库测试在升级过程中是至关重要的一步,根据生产过程中的查询数额来进行性能测试,将结果存到 pg_stat_statement 表中。这些都是在同一个数据集中运行的,一次是在 9.6 版本,一次是在 11 版本的迭代。这一步过程可以在下面这个公共的 issue 中找到:
最后,根据 OnGres 在这一基准测试上的工作,GitLab 将在未来跟进新的基准测试。
在升级项目中,升级团队坚持使用自动化和基础架构及代码工具(IaC)。所有流程必须全部自动化,以减少在维护窗口的人为失误。pg_upgrade 所有的运行步骤都可以在这个 GitLab 的 pg_upgrade 的模板 issue 上找到详细说明。
GitLab.com 的环境由 Terraform 和 Chef 共同管理,所有的升级自动化都是用 Ansible 2.9 的 playbook 和 roles 编写的,我们用了两个 ansible-playbook 来完成升级自动化:
一个 ansible-playbook 控制流量和应用:
另一个 ansible-playbook 运行升级过程:
playbook 以交互方式逐个运行所有任务,让程序员得以在任意给定执行点跳过或暂停程序。参与 staging 测试和迭代的所有团队成员都要过目升级过程中的所有步骤,staging 环境让我们通过演习提前找到升级过程中潜在的漏洞。而执行和迭代 staging 中自动化过程则让我们实现了 PostgreSQL 9.6 版本至 11 版本的基本无缺陷升级。
为完成本次的版本升级,GitLab 的 QA 团队将部分测试中发现的问题反馈给我们,这一部分的工作可以在这条 issue 中找到。
升级工作的第一步是“预升级”,这里涉及到预留给回滚的示例。我们做了相应分析,以确保新的集群可以不丢失吞吐量的情况下,以 8 个示例为起点,保留 4 个通过标准 Patroni 集群同步的 9.6 版本示例,为后续可能需要的回滚情况准备(共计 12 个实例)。
在这个阶段,我们还需要停止依赖 PostgreSQL 的服务,诸如 PgBouncer、Chef 客户端,以及 Patroni 服务。
在正式开始更新前,必须要告知 Patroni,避免任何虚假 leader 选举,通过 GCP 快照(通过对应低级备份API 获得)进行一致的备份,并通过运行Chef 应用新的设置。
首先,停止所有节点。
然后,运行以下检查:
一旦主节点数据升级完毕,就会触发 rsync 进程以同步所有副本数据。在升级完成后,启动 Patroni 服务,这样所有副本都能轻松更新至新集群的配置。
通过 Chef 安装二进制文件,新集群在版本方面的设置是在同一个 MR 中定义的,MR 源自 GitLab.com,可以安装用于数据库中的拓展项。
最后一个阶段则包括恢复流量、运行初始的真空期,以及最后的启动 PgBouncer 和 Chef 客户端服务。
到了最后,我们为运行生产线上升级做好了万全准备,团队在周日一早 8:45 UTC 开始会议(对有的人来说是晚上)。服务将最多下线两小时,当最终的通知下达后,工程团队终于可以开始进行。
升级过程由停止所有流量及相关服务开始,这是为了避免用户在更新中途访问网站。
下面图表显示在服务更新之前,维护期间(图标中的空白部分)、以及维护结束、流量恢复后的流量和 HTTP 数据统计。
GitLab.com 上的数据统计图,从维护开始到结束
整个流程共花费四个小时,其中仅包括两小时断线时间。
此外,我们录下了PostgreSQL 更新的全过程并发布在 GitLab Unfiltered 上。
原文链接:
https://about.gitlab.com/blog/2020/09/11/gitlab-pg-upgrade/