我现在有一个中型项目,该项目已经接近“为客户演示提供草率咖啡因的原型”阶段的结尾,并过渡到“思考未来”阶段。该项目由具有软件和固件的基于Linux的设备以及中央管理Web服务器组成。目前存在10个原型,预计产量将低至1000左右。
我不熟悉自动更新技术,而且时间紧迫,所以我迅速制定了自己的软件部署/自动更新策略,坦率地说,它很糟糕。当前由以下各项组成:
- 带有生产发行分支的托管git repo(GitLab)(请注意,Web服务器源也位于此相同的repo中,还有其他一些东西)。
- Web界面上的“部署更新”按钮,该按钮:
- 从生产发行分支将最新版本拉到本地回购区域,并将其复制到临时软件包准备登台区域。
- 在登台区域中运行清理脚本(存储在仓库中)以删除不相关的源文件(例如服务器源,固件源等)和.git文件。
- 将当前git哈希写入更新包中的文件(目的将在下面变得清楚)。
- 如果一切顺利,它将用gzip压缩并准备好使用,方法是使用同名文件覆盖以前的gzip压缩包,然后删除暂存区。
- 请注意,服务器上现在有两个当前设备软件的副本,它们有望同步:最新生产分支上的完整本地git repo和一个现成的gzip压缩包,现在假定该软件包代表了以下内容:相同的版本。
- 设备上的软件独立包含在名为的目录中
/opt/example/current
,该目录是该软件当前版本的符号链接。 - 引导时设备上的自动更新功能:
- 检查
do_not_update
文件是否存在,如果文件存在,则不采取进一步措施(有关开发设备,请参见下文)。 - 从上述文本文件中读取当前提交哈希。
- 使用该哈希作为查询参数向服务器发出HTTP请求。服务器将以304(哈希为当前版本)响应,或者将提供压缩后的更新程序包。
- 如果收到更新包,则
/opt/example
通过以下方式安装:- 提取更新的软件信息,将其命名为
stage
。 - 从更新包运行安装后脚本,该脚本执行诸如对该更新进行必要的本地更改等操作。
- 将当前软件的根文件夹复制到
previous
(previous
如果存在,则首先删除现有文件夹)。 - 将
stage
文件夹复制到latest
(latest
如果存在,则首先删除现有文件夹)。 - 确保
current
符号链接指向latest
。 - 重新引导设备(固件更新(如果存在)在重新引导时应用)。
- 提取更新的软件信息,将其命名为
- 检查
在新建设备上也存在初始部署的问题。这些设备当前基于SD卡(存在其自身的问题集,不在本文范围内),因此此过程包括:
- 存在一个SD映像,上面带有该软件的某些稳定早期版本。
- 从该映像创建SD卡。
- 首次启动时,会进行各种首次特定于设备的(基于序列号)初始化,然后自动更新程序将照常获取并安装该软件的最新生产版本。
另外,我需要开发设备的支持。对于开发设备:
- 完整的本地git repo会保留在设备上。
- 该
current
符号链接指向开发目录。 do_not_update
存在一个本地文件,该文件可防止自动更新程序随生产更新一起删除开发代码。
现在,从理论上讲,部署过程应为:
- 一旦准备好部署代码,就将其推送到发布分支。
- 按下服务器上的“部署更新”按钮。
- 该更新现已生效,设备在下次检查时将自动更新。
但是,实践中存在大量问题:
- Web服务器代码与设备代码位于相同的存储库中,并且服务器具有我要执行的本地git存储库。最新的Web服务器代码与最新的设备代码不在同一分支上。目录结构有问题。当“部署更新”按钮从生产分支中提取最新版本时,会将其提取到服务器代码的子目录中。这意味着当我从头开始部署到服务器时,我必须通过将设备生产分支捕获到该子目录中来手动“播种”该子目录,因为如果我不进行部署,则可能是由于git user错误造成的。从父目录的Web服务器分支中提取设备代码。我认为这可以解决,方法是使暂存区不是服务器本地git repo的子目录。
- Web服务器当前不永久维护设备软件的git哈希。在服务器启动时,它会
git rev-parse HEAD
在其本地设备软件仓库中执行,以检索当前哈希。由于某些原因,我无法全神贯注,这还会导致大量逻辑错误,在此不再赘述,足以说明有时重新启动服务器会使事情搞砸,特别是如果服务器是全新的并且没有生产分支仓库已被撤消。如果需要的话,我很乐意分享该逻辑的源代码,但是这篇文章越来越长。 - 如果清理脚本(服务器端)由于某种原因而失败,则服务器将拥有最新的存储库,但同步/丢失更新包不完整,因此
git rev-parse HEAD
将返回与实际不匹配的哈希服务于设备,并且必须在服务器命令行上手动更正此处的问题。即服务器不知道更新程序包是不正确的,它只是始终以纯属为前提。结合以上几点,使服务器在实践中极为脆弱。 - 最大的问题之一是:设备上当前没有运行单独的updater守护程序。由于等待wifi上网的复杂性和最后一刻的黑客行为,它是检查和更新设备的主要设备控制软件。这意味着,如果某种程度上未经测试的版本将其投入生产,并且控制软件无法启动,则实际上存在的所有设备都是砖砌的,因为它不再能够自我更新。这将是生产中的绝对噩梦。如果单个设备在不幸的时间断电,则同样的处理方法。
- 另一个主要问题是:不支持增量更新。举例来说,如果设备一段时间没有打开,那么下次对其进行更新时,它会跳过一系列发行版本,因此它必须能够进行直接的版本跳过更新。更新部署的结果是确保任何给定更新都可以应用在任何给定过去版本之上的噩梦。此外,由于使用git散列来标识版本而不是版本号,因此目前尚无法对版本进行字典式比较以促进增量更新。
- 我当前不支持的一个新要求是,将存在一些必须在管理服务器端配置的每设备配置选项(键/值对)。我不介意以某种方式将这些每设备选项以与软件更新相同的HTTP请求提供给设备(也许我可以将其封装在HTTP标头/ Cookie中),尽管我不太担心,因为我可以始终使其成为单独的HTTP请求。
- 由于存在两个(将来会有更多)硬件版本,因此会有些复杂。当前硬件的当前版本实际上是作为环境变量存储在其初始SD映像上(它们无法自我识别),并且所有软件均设计为与设备的所有版本兼容。基于此环境变量选择固件更新,并且更新包包含所有硬件版本的固件。尽管有点笨拙,但我可以接受。
- 当前还没有办法将更新手动上传到设备(长话短说,这些设备中有两个wifi适配器,一个可以连接到Internet,一个以AP模式供用户用来配置设备;将来我打算在设备的本地Web界面中添加“更新软件”功能。这不是什么大问题,但确实会对更新安装方法产生一些影响。
- 一堆其他的挫败感和普遍的不安全感。
所以...很长。但是我的问题可以归结为:
如何正确,安全地执行此操作?我可以对现有流程进行一些小的调整吗?我是否可以使用经过时间考验的策略/现有系统,这样就不必推出自己糟糕的更新系统了?或者,如果我必须自己动手做,为了使部署/更新过程安全成功,必须做些什么?我还必须能够将开发设备包括在内。
我希望问题清楚。我意识到这有点模糊,但是我100%地确定这是一个之前已经解决并成功解决的问题,我只是不知道当前接受的策略是什么。