亚马逊AWS官方博客

基于 Amazon S3 的海量低成本 Git 仓库大文件存储方案

Git 是全球流行的、免费的、开源的版本控制系统。Git 使用中不可避免的涉及到大文件版本管理,大文件会使得 Git 操作变慢,且占用大量存储空间,增加存储、带宽费用。Amazon Simple Storage Services(Amazon S3)作为专业的对象存储服务,最适合存放这些文件对象。所以,如何将 Git 与 Amazon S3 结合起来使用达到降本增效? 本文将给出答案。

Git 大文件场景痛点

Git 大文件管理是一个存在很久的文件仓库管理问题, 一些常见的场景如媒体文件、可执行文件、第三方依赖包、压缩文件、数据库、AI 模型等等。随着近年来大语言模型、文生图模型等 AI 模型的迅猛的发展,AI 模型文件动不动就是以 GB 甚至上百 GB 为单位,使得该问题更加突出:

  • 下载慢:Git 中使用大文件,Git 需要存储存储库中每个文件的全部内容,包括大文件,这会使克隆、推送和拉取存储库等都需要很长时间。同时,Git 本身设计上没有考虑大文件场景,其上次下载的效率本身也不高。
  • 占用大量空间/费用:如果存储库包含大量大文件,它会很快占用光服务器上的存储空间,意味大量存储、下载费用的占用,同时,我们还得经常要进行扩容维护。
  • 软件限制与瓶颈:不少 Git 仓库服务器、SaaS 服务都有最大文件或者最大仓库大小限制。大文件在这种场景下,还得人工分割,使用时再人工组合。

Amazon S3 对象存储服务

Amazon S3 是一种对象存储服务,并且默认提供多可用区、99.999999999%(11 个 9)的持久性。

作为专业的存储存储服务,Amazon S3 最大可支持高达 5TB 的文件。并且,Amazon S3 桶不限制对象数量与总存储量,无需任何扩容维护。

同时,Amazon  S3 提供丰富的存储类型,通过生命周期管理,可以最大限度地降低存储成本。结合 Amazon S3 Transfer Acceleration 或 Amazon CloudFront,能提供全球加速服务,提升下载速度。特别是结合 Amazon CloudFront,还能进一步降低下载流量费。

那么,能完美解决 Git 大文件痛点的 Amazon S3,能不能直接用于 Git 呢? 其实针对这个场景,陆陆续续有好多方案实现:

手工维护方式

大文件存储于 Amazon S3,Git 仓库内文件只保存 Amazon S3 路径。该方案简单易用,没有任何依赖。上传大文件时,先存于 Amazon S3,然后对应文件内存放 Amazon S3 路径。下载时,Git 下载的只是记录地址的,要真正使用到该大文件的时候,再从 Amazon S3 下载。

该方案问题也很明显,所有文件对应关系、版本关系都需要人工维护。Amazon S3 文件的变化没能及时反馈到 Git 记录文件,会导致版本混乱,产生业务风险。

Amazon S3 桶即仓库

该方案直接使用 Amazon S3 桶作为远程仓库,全部仓库都位于 Amazon S3,是一个充满想象力的方案。Amazon S3 本身也支持版本控制,理论上能完美地支持 Git,最终我们不需要任何代码仓库服务,只需要 Amazon S3 本身。

当然,实际使用中,比如最终一致性管理、权限管理等众多问题需要管理。同时,因为 Git 会产生大量的极小文件、频繁读写,产生的 API 费用相对存储费用比例也会上升,所以,还有很多需要优化的空间。这个方案的代表方案有:

基于 LFS 方案

基于 LFS 的大文件存储 Amazon S3 方案,是一种 Git 手工维护大文件信息的工具化方案。 大文件存储于 Amazon S3,正常的代码文件存放于原 Git 仓库。其兼顾了 Git 的成熟稳定,对客户现有 CI/CD 流程,研发工具几乎没有影响,同时还能将仓库中的存储、流量大头负载迁移到更加高可用的 Amazon S3。下面我们将逐步展开解释。

Git 大文件系统扩展功能介绍

Git LFS(Large File Storage,大文件存储)是使用 Git 管理大文件的命令行扩展与规范。 Git LFS 将大文件内容存储于远端文件服务器,Git 仓库本身只包括文件地址信息于文件 Hash。通过这种方式,Git 仓库本身就没有大文件需要管理,可以节省磁盘空间和带宽,并且可以更轻松地与其他团队成员进行项目协作。

Git LFS 使用非常简单,在安装该扩展后,使用`git lfs track <文件>` 添加需要追踪的大文件,然后`git add .gitattributes`添加对应的 lfs 元信息文件。Git LFS 会根据配置文件,将大文件上传到指定存储服务器,或者从存储服务器下载大文件。

图 1 LFS 流程所示,对于本地的代码文件,Git 会直接推送至代码仓库服务端;对于大文件,如 Photoshop 的设计文档*.psd,LFS 会将文件上传到大文件存储服务(如 Amazon S3),然后再生成链接信息文件,最后将链接信息文件上传到代码仓库。

图 1 LFS 流程

自行构建 LFS-Server

Git LFS 标准协议规定了 Git LFS 服务端的 API 规范。最自然的实现是通过构建 HTTP API 服务器,直接配置 Git LFS Endpoint 来实现 LFS 存储于 Amazon S3。

这种方案很多。比如在亚马逊云科技上,通过 Amazon API Gateway,配合 Amazon Lambda,实现全流程 Serverless 的 LFS 协议。客户只需要为 Amazon S3 存储成本,以及每次 git lfs push/pull 产生的 Amazon  API Gateway、Amazon Lambda 费用付费,且后续无需维护。具体可以参考对应的 vitalibo git-lfs 开源代码以及对应的亚马逊云科技部署架构图 2 基于亚马逊云服务的 LFS 服务器 vitalibo/git-lfs

图 2 基于亚马逊云服务的 LFS 服务器 vitalibo/git-lfs

自建服务方式的好处是只需要配置 LFS Endpoint 即可。但是,仍然需要客户承担一定的服务端计算成本(Amazon API Gateway、Amazon Lambda)。如果对这部分计算成本不敏感的客户,可以直接使用该方案。

基于云端 SaaS LFS 服务

很多 Git 服务商也提供了 SaaS 化的 LFS 服务,只需要购买服务,配置好 LFS Endpoint 即可使用。以 GitHub 为例,按 Data Pack 进行购买,每个 Data Pack 包含 50G 存储+50G 带宽,成本也不低。同时,国内也存在访问稳定性问题。

基于 Custom Transfer 构建自定义存储端

由于 API 标准规定限制了上传/下载的灵活性,必要要构建 API 服务器才能实现。Git LFS 新提出了一种基于 custom transfer 的接口。该接口规范不依赖于 API 服务器,仅需要配置一个可执行程序。对于 LFS 的上传、下载,Git LFS 将通过标准的内容格式,调用该可执行程序,然后由该可执行程序负责具体的大文件上传、下载。这就给各种存储对接 Git LFS 提供了无限可能。基于 LFS S3 Custom Transfer 的方案也涌现出来。

Git LFS Amazon S3 Custom Transfer 方案

Git LFS Custom Transfer 调用配置要的可执行程序,并从定向可执行程序的标准输入/输出,通过该标准输入/输出实现与该程序间的消息交互,下发上传、下载指令,接收任务进度情况。下文,我们采用一个最简单的 Git LFS S3 对接开源实现,来演示如何快速对接 Amazon S3。

方案部署架构

该方案的具体部署配置如图 3 LFS Custom Transfer Type

  1. Git client 配置 LFS custom transfer 可执行程序,作为 hook
  2. 对于 LFS track 的大文件,调用 hook,并通过 custom transfer 协议与可执行文件通信
  3. 可执行文件负责具体的上传、下载,并返回结果

图 3 LFS Custom Transfer Type

配置使用简介

  1. lfs-s3 没有提供编译好的可执行程序供直接使用。我们需要下载并编译安装  lfs-s3,可以参考一下命令
    1.	git clone https://github.com/nicolas-graves/lfs-s3
    2.	cd lfs-s3
    3.	go build
    4.	sudo cp lfs-s3 /usr/local/bin
    
  1. 配置 Git LFS custom transfer 相关信息
    1.	git config --add lfs.customtransfer.lfs-s3.path lfs-s3
    2.	git config --add lfs. standalonetransferagent lfs-s3
    
  1. 配置 Amazon S3 相关的环境变量。当然,我们也可以配置到启动文件里面,或者,修改代码减少配置。现在原始的仓库,我们仍然需要配置如下信息
    1.	export AWS_REGION=<aws-region-code>
    2.	export AWS_ACCESS_KEY_ID=<aws-access-key>
    3.	export AWS_SECRET_ACCESS_KEY=<aws-security-key>
    4.	export AWS_S3_ENDPOINT=https://s3.<aws-region-code>.amazonaws.com
    5.	export S3_BUCKET=<bucket-name>
    6.	export S3_USEPATHSTYLE=False
    
  1. 通过创建 Git 仓库测试验证
    1.	mkdir lfs-test
    2.	cd lfs-test
    3.	# 创建一个 1M 的大文件
    4.	dd if=/dev/zero of=ai.model bs=1K count=1024
    5.	# 创建 lfs 追踪
    6.	git lfs track ai.model
    7.	# 把 lfs 配置管理文件加入 git
    8.	git add .gitattributes
    9.	# 设置远程 git 仓库
    10.	git remote set-url origin git@github.com:someone/lfs-s3.git
    11.	git commit -am "test lfs"
    12.	# 上传测试
    13.	git push -u origin master
    
  1. 上传结果检查。以 GitHub 为例,我们可以看到上传的文件内容为:
    1.	version https://git-lfs.github.com/spec/v1
    2.	oid sha256:30e14955ebf1352266dc2ff8067e68104607e750abb9d3b36582b8af909fcb58
    3.	size 1048576
    

    其中 oid 为文件对应版本的 hash 值。最终 Amazon S3 上存储的文档将为该名字。后面的 size 为文件大小(这里是 1M)。在 Amazon S3 桶中,我们可以找到该 hash 对应的文件对象,如图 4 S3 object 信息所示。

图 4 S3 object 信息

接下来,我们再进行下载测试:

1.	rm -rf lfs-test/
2.	# 环境变量与 git 配置保持不变
3.	git clone git@github.com:someone/lfs-test.git 

然后,我们通过 sha256 hash,检测下载的大文件内容是否正确:

1.	cd lfs-test/
2.	sha256sum ai.model 
3.	30e14955ebf1352266dc2ff8067e68104607e750abb9d3b36582b8af909fcb58  ai.model

可以看到,Hash 值与原来的文件一致。文件成功下载。

LFS Amazon S3下载优化方案

前述方案在用户实际使用中,因为 Amazon S3 桶有区域属性,文件下载还要收取普通出网流量费用,最终使用下来下载速度不是最优,同时还会造成不少流量费用。因而,怎么样进一步优化呢?

Amazon CloudFront 是亚马逊云科技提供的一项内容交付网络(CDN)服务,可为客户提供全球低延迟、高传输速度的 Web 缓存与加速服务,Amazon CloudFront 已经与包括 Amazon S3 在内的亚马逊云科技云服务进行了无缝对接,同时,Amazon CloudFront 的出网流量费用低至 Amazon S3 的三分之一。如果通过 LFS 上传、下载分开实现,将大幅提升终端用户下载体验的同时,还减少三分之二的网络传输成本。下面我们展开次方案实现。

部署架构优化

图 5 LFS 上传/下载分割优化所示,我们需要对 Git LFS Custom Transfer 可执行程序进行改造,对 Git LFS 发过来的请求事件,进行区分对待。对于上传请求,直接调用 S3 SDK 上传文件;对于下载请求,转到请求对应的 Amazon CloudFront 下载。

图 5 LFS 上传/下载分割优化

代码改造

为了实现上述功能,我们需要对代码进行改造。Git Custom Transfer 协议本身并不复杂,lfs-s3 代码也不复杂,因而,我们 clone 了一份代码,然后对其中的下载部分进行了添加&功能改造。

  • 增加对 `S3_BUCKET_CDN` 环境变量的检查。如果下载的时候,发现配置有该环境变量,则走 CloudFront 下载。
  • 增加 CloudFront 下载的支持:根据请求内容,生成对应的 CloudFront URL,然后进行下载,并保存文件内容。

相应代码修改,可以参考我们修改后的 fork 代码

CloudFront 配置

具体配置参考 Amazon CloudFront 配置 S3 Bucket 源站相关的文档。配置完成后,我们拿到 Amazon CloudFront 分配的域名。

拿到域名后,设置环境变量:`export S3_BUCKET_CDN=dxxxxxxx.cloudfront.net`

测试

测试过程与前面配置使用简介章节方法一致,为了检验 Git LFS 是否通过 CloudFront 下载文件,我们需要配置 CloudFront 访问日志。配置完成后,后续产生的访问记录,都会存放与 Amazon S3 桶中,方便我们验证。

在功能测试完成后,我们下载 Amazon S3 桶访问日志,检查 CloudFront 的访问日志。可以看到对应的访问记录,证实 Git LFS 确实通过 CloudFront 下载了。

1.	2023-09-26 15:37:51 HIO50-C2 1050157 66.66.66.66 GET dxxxxxxxxxxxx.cloudfront.net /30e14955ebf1352266dc2ff8067e68104607e750abb9d3b36582b8af909fcb58 200 - Go-http-client/2.0 - - Miss oMNx9x_wsFy-666666666666== dxxxxxxxxxxxx.cloudfront.net https 103 0.164 - TLSv1.3 TLS_AES_128_GCM_SHA256 Miss HTTP/2.0 - - 39030 0.135 Miss application/octet-stream 1048576 - -

历史库迁移

对于我们现有的历史代码库,一般来说,我们会有一定依赖:CI/CD pipeline,开发人员使用习惯,不愿意改造等等问题。幸运的是,Git LFS 改造也非常简单。除了正常的环境与配置的改造,只需要使用 git lfs migration,执行 `git lfs migrate import –include=<文件/文件匹配>` 即可实现迁移到 Git LFS。

对于更复杂的场景,如将大于某个尺寸的文件迁移到 Git LFS,也可以通过一些 Git 命令组合来实现:

1.	 git lfs migrate import --include="$(git ls-tree -lr "$(git cat-file -p HEAD | grep '^tree' | awk '{ print $2 }')" | awk '$4 > 1000 { print $5 }'  | tr '\n' ',')"

总结

本文介绍 Git LFS 与 Amazon S3 对象存储结合的各类办法,着重介绍了其中 Git LFS custom transfer 方式的轻量化方案。该方案解决了 Git 大文件下载慢、存储费用高、面临各种限制等众多用户痛点。接下来本文更进一步对方案进行了优化,通过上传下载分流,将下载转到 Amazon CloudFront 进一步优化了用户体验与使用成本,并提供了优化后的代码。

当然,对应的代码,我们还可以根据自己的使用场景,进行进一步的优化,如:

  • 访问云资源的环境变量,转到通过 Role,实现更安全的客户端访问
  • 对 CloudFront 做安全限制,通过 Amazon WAF 或者 URL 签名等方式进一步强化安全
  • 添加 Amazon S3 生命周期管理,对于长期未访问的文件,转入 Amazon S3 Glacier Instant Retrieval 进一步节省费用。

参考文献

  1. Amazon CloudFront 配置 Amazon S3 源站 https://docs.thinkwithwp.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.SimpleDistribution.html
  2. Git LFS API 服务器协议标准 https://github.com/git-lfs/git-lfs/tree/main/docs/api#readme
  3. Git LFS 官网 https://git-lfs.com/
  4. Git LFS Custom https://github.com/git-lfs/git-lfs/blob/main/docs/custom-transfers.md
  5. lfs-s3 工具源代码 https://github.com/nicolas-graves/lfs-s3
  6. 添加对 Amazon CloudFront 支持的 lfs-s3 工具 https://github.com/kealiu/lfs-s3
  7. Git lfs migration 迁移命令文档 https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-migrate.adoc

本篇作者

刘科

西云数据解决方案架构师,15+年 IT 研发、管理经验,曾就职于知名通信设备厂商、头部互联网企业,有丰富、广泛的系统架构与研发管理经验。擅长帮助企业打造完善研发 Devops 体系,构建网络与安全体系,帮助企业业务产品项目落地。

宋孜攀

西云数据解决方案架构师,负责西云数据用户的亚马逊云科技平台架构设计和技术支持工作,软件研发及云原生架构设计经验丰富,熟悉云计算技术演进,目前专注于云上无服务应用架构及解决方案的研究,具有世界知名软件公司工作经历,及创业经历。