失败时

2020-12-29 16:19:43

昨天,该站点关闭了大约四个小时。投诉开始从我成千上万的热心追随者那里滚滚而来,促使我采取行动。在我解构出问题的地方,如何解决它以及如何防止再次发生问题的过程中加入Joinme。

该网站由Hugo(一个用Go语言编写的静态网站生成器)构建而成。它使用了MatúšNámešný修改过的Nix主题版本。它是通过GitLab的CI / CD(ContinuousIntegration / Continuous Deployment)服务自动部署的。每次我将提交推送到远程存储库时,服务器都会运行我编写的部署脚本。该脚本生成网站,然后将其上传到我的虚拟主机。

自从流程开始工作以来,到目前为止我还没有遇到任何问题。

昨天晚上8:22,我将良性提交推送到远程存储库。有问题的提交只是删除了我一直在撰写的帖子草稿,并决定不发布。与所有推送的提交一样,这触发了网站的构建和推送。

几个小时后,我去了我的网站,发现一些奇怪的东西:主页是404ing。其他所有页面也应该放在那儿。因此,我查看了我的CI日志,发现:

第一个还可以;即使理想情况下也不会发生故障。第二个令人担忧:为什么会发生?第三个有可能造成灾难性的后果。

对我来说幸运的是,后果不多。据我所知,我的网站很少有访问者。此外,由于它是静态网站,所以它并不像我的网站为我运行重要的工具或服务。

正如我上面提到的,在这四个小时中,拥有该站点的整个知识库的.git目录可供所有人浏览,网址为https://reeshill.net/.git/。当然,这意味着不仅可以公开访问存储库的当前状态,而且所有过去的版本历史记录也都可以公开访问。

我的CI脚本需要通过我的Web主机进行身份验证才能上载生成的网站,并使用应用专用密码进行此操作。对于脚本,此密码需要存储在CI服务器可以访问的位置。朴素的解决方案是将密码硬编码到运行CI的脚本中。如果执行了此操作,即使以后删除了该密码,该密码也将存在于存储库的历史记录中。如果是这种情况,攻击者将有足够的时间在版本历史记录中发现此漏洞并将恶意内容上传到我的Web主机(例如Javascript加密货币矿工)。

幸运的是,过去我足够聪明,不会在版本控制中放任何秘密。密码实际上是由GitLab存储的,并作为环境变量提供给CI服务器。我对GitLab的信任比对我自己的信任更加重要。

两次大错误立即导致了此失败。如果它们中的任何一个权利都正确,那么损害将是不存在的或极其微不足道的,但由于它们都设法一次失败,因此后果更大。

失败的直接原因是Hugo安装开始失败。我正在使用包含lftp的Alpine Linux Docker镜像,该镜像用作FTP客户端,但是该镜像不包含Hugo,当我设置它时我是害怕使用它,因此CI每次运行时都会从网络上安装Hugo。Alpine存储库中的某些内容必须已更改,因为突然运行的hugo会导致错误

这应该没什么大不了的。 CI应该会失败,然后我会修复Hugo安装。但是由于问题2,整个情况逐渐失控。

当我在2018年4月编写用于构建和上传网站的脚本时,我还不了解Shell脚本的工作原理。至关重要的是,我不了解退出代码。运行shell命令时,部分结果是退出代码:如果命令成功,则该数字为0,如果失败,则为其他值。

默认情况下,即使一个或多个命令失败,包含一系列命令的脚本也会按顺序运行所有这些命令。有关示例,请参见以下脚本:

如果命名目录不存在,第二个命令(ls)将失败,但是脚本将继续运行。这是运行上述脚本的输出:

$ ./steamroll.sh启动脚本!ls:不存在的目录:没有这样的文件或目录上一条命令的退出代码为1

太恐怖了!在大多数编程语言中,如果程序遇到错误,它将崩溃或显示某种异常,从而终止执行。由于默认的Shell行为是继续运行,而不考虑失败状态,因此我的脚本未能生成该站点,但继续执行下一个反正一步。

在我的情况下,当lftp无法上载该目录(应该是当前目录的子目录)时,它应该上载当前目录,即存储库根目录。看起来像这样:

/ builds / jarhill0 / homepage / public:没有这样的文件或目录转移文件`README.md'转移文件`ci-upload.sh'转移文件`config.toml'制作目录`.git'传输文件`.git / FETCH_HEAD'传输文件`.git / HEAD' [etc。]

最终,导致命令失败的原因是我的脚本无法停止。应当预料到会有小故障,但是当它们发生时,应该有明确的故障指示,并且该过程不应继续进行。

由于我仍然能够在本地计算机上构建网站,因此我做了并上传了它。那给了我时间来解决潜在的问题。

如果我的构建之一没有成功,则应该在脚本的退出状态中反映出来。

根据set的手册页,将以下行添加到脚本顶部将执行以下操作:

这正是我们想要的!一旦命令失败,脚本应失败。设置此选项后,将来可能发生的任何故障都将以失败状态停止CI作业。完善!

#!/ bin / sh set -e echo'启动了脚本!ls目录-不存在的echo'上一个命令的退出代码是' " $? "

现在我的构建状态正确地反映了正在发生的故障,我需要使构建成功。

快速搜索并没有帮助我弄清楚是什么原因导致了Hugo的错误安装,而且我不想进一步挖掘,因为我知道我在滥用Docker.Docker的全部意义在于它会导致可重现的状态。我不必每次都在Docker映像之上安装新的依赖项,而应该从一开始就在映像中放置所有必要的依赖项。因此,我硬着头皮想出了如何制作(实际上是改编)Dockerfile。现在,我所有的CI版本都将从已经安装的Hugo和lftp开始。并不需要花很长时间,现在我又删除了一个失败源!

有些故障(例如我的脚本中由set -e修复的bug)可以在休眠状态下搁置一段时间(在我的情况下是一年半),然后在出现其他故障时暴露出来。 经常不会想到回去检查它是否如我们期望的那样工作。 而且很难在第一时间得到一切。 我很高兴这次失败导致我使用Docker映像的方式得到了改善。我很高兴脚本已修复,因为现在任何其他失败都将被发现,并且我会得到提醒。 最重要的是,我很高兴一切在开始时就配置得足够好,以至于我的网站没有发生任何不良情况。