Laravel零停机时间部署

本文概述

在更新实时应用程序时, 有两种根本不同的解决方法。

在第一种方法中, 我们对系统状态进行增量更改。例如, 我们更新文件, 修改环境属性, 安装其他必需品, 等等。在第二种方法中, 我们拆除了整个机器, 并使用新映像和声明性配置(例如, 使用Kubernetes)重建了系统。

Laravel部署变得容易

本文主要介绍相对较小的应用程序, 这些应用程序可能无法托管在云中, 尽管我将提到Kubernetes如何在”无云”场景之外为部署提供极大帮助。我们还将讨论一些成功执行更新的一般问题和技巧, 这些问题和技巧可能适用于多种不同情况, 而不仅限于Laravel部署。

出于演示目的, 我将使用Laravel示例, 但请记住, 任何PHP应用程序都可以使用类似的方法。

版本控制

对于初学者而言, 至关重要的一点是, 我们必须了解当前在生产环境中部署的代码版本。它可以包含在某些文件中, 或者至少包含在文件夹或文件的名称中。至于命名, 如果我们遵循语义版本控制的标准做法, 则可以在其中包含更多信息, 而不仅仅是一个数字。

查看两个不同的发行版, 这些添加的信息可以帮助我们轻松地了解它们之间引入的更改的性质。

该图显示了语义版本控制的说明。

对版本进行版本控制始于版本控制系统, 例如Git。假设我们已经准备好要发布的版本, 例如1.0.3版。在组织这些版本和代码流时, 有不同的开发风格, 例如基于主干的开发和Git流, 你可以根据团队的偏好和项目的具体情况来选择或混合。最后, 我们很可能最终会在主分支上以相应的方式标记我们的发行版。

提交后, 我们可以创建一个简单的标签, 如下所示:

git标签v1.0.3

然后在执行push命令时包含标签:

git push <来源> <分支>-标签

我们还可以使用其哈希将标签添加到旧提交。

使发行文件到达目的地

即使仅复制文件, Laravel部署也需要时间。但是, 即使时间不会太长, 我们的目标也是实现零停机时间。

因此, 我们应避免就地安装更新, 并且不应更改正在实时提供的文件。相反, 我们应该部署到另一个目录并仅在安装完全准备就绪后进行切换。

实际上, 有各种工具和服务可以协助我们进行部署, 例如Envoyer.io(由Laravel.com设计师Jack McDade设计), Capistrano, Deployer等。我尚未在生产中使用所有工具和服务, 因此我无法提出建议或进行全面比较, 但让我展示这些产品背后的想法。如果其中一些(或全部)不能满足你的要求, 则始终可以创建自定义脚本, 以最佳方式自动执行该过程。

出于演示目的, 我们假设我们的Laravel应用由Nginx服务器通过以下路径提供:

在/ var / WWW /演示/公

首先, 我们需要一个目录来在每次部署时放置发布文件。另外, 我们需要一个符号链接, 它指向当前的工作版本。在这种情况下, / var / www / demo将用作我们的符号链接。重新分配指针将使我们能够快速更改发行版。

Laravel部署文件处理

如果我们正在处理Apache服务器, 则可能需要在配置中允许以下符号链接:

选项+ FollowSymLinks

我们的结构可以是这样的:

/opt/demo/release/v0.1.0
/opt/demo/release/v0.1.1
/opt/demo/release/v0.1.2

可能有些文件需要我们通过不同的部署来保留, 例如日志文件(如果我们不使用Logstash, 则显然)。对于Laravel部署, 我们可能想要保留存储目录和.env配置文件。我们可以将它们与其他文件分开, 而使用它们的符号链接。

为了从Git存储库中获取发布文件, 我们可以使用克隆或存档命令。有些人使用git clone, 但是你不能克隆特定的提交或标记。这意味着将提取整个存储库, 然后选择特定标签。当存储库包含许多分支或较大的历史记录时, 其大小比发行归档文件大得多。因此, 如果你在生产中不需要特别的git repo, 则可以使用git archive。这使我们可以通过特定标签仅获取文件存档。使用后者的另一个优点是我们可以忽略生产环境(例如测试)中不应该存在的一些文件和文件夹。为此, 我们只需要在.gitattributes文件中设置export-ignore属性。在OWASP安全编码实践清单中, 你可以找到以下建议:”在部署之前, 删除测试代码或任何非生产目的的功能。”

如果我们从源代码版本控制系统中获取发行版, 则git archive和export-ignore可以帮助我们满足这一要求。

让我们看一个简化的脚本(在生产中需要更好的错误处理):

deploy.sh

#!/bin/bash
# Terminate execution if any command fails
set -e

# Get tag from a script argument
TAG=$1
GIT_REMOTE_URL='here should be a remote url of the repo'
BASE_DIR=/opt/demo

# Create folder structure for releases if necessary
RELEASE_DIR=$BASE_DIR/releases/$TAG
mkdir -p $RELEASE_DIR
mkdir -p $BASE_DIR/storage
cd $RELEASE_DIR

# Fetch the release files from git as a tar archive and unzip
git archive \
    --remote=$GIT_REMOTE_URL \
    --format=tar \
    $TAG \
    | tar xf -

# Install laravel dependencies with composer
composer install -o --no-interaction --no-dev

# Create symlinks to `storage` and `.env`
ln -sf $BASE_DIR/.env ./
rm -rf storage && ln -sf $BASE_DIR/storage ./

# Run database migrations
php artisan migrate --no-interaction --force

# Run optimization commands for laravel
php artisan optimize
php artisan cache:clear
php artisan route:cache
php artisan view:clear
php artisan config:cache

# Remove existing directory or symlink for the release and create a new one.
NGINX_DIR=/var/www/public
mkdir -p $NGINX_DIR
rm -f $NGINX_DIR/demo
ln -sf $RELEASE_DIR $NGINX_DIR/demo

为了部署我们的发行版, 我们可以执行以下命令:

deploy.sh v1.0.3

注意:在此示例中, v1.0.3是我们发行版的git标签。

生产Composer?

你可能已经注意到该脚本正在调用Composer来安装依赖项。尽管你在许多文章中都看到了这一点, 但是这种方法可能会有一些问题。通常, 最佳做法是创建应用程序的完整版本, 并通过基础结构的各种测试环境来推进此版本。最后, 你将拥有经过全面测试的构建, 可以将其安全地部署到生产中。即使每个构建都可以从头开始复制, 但这并不意味着我们应该在不同阶段重建应用。当我们在生产环境中安装composer时, 这实际上与经过测试的版本并不相同, 这可能会出错:

  • 网络错误可能会中断下载依赖项。
  • 图书馆供应商可能并不总是遵循SemVer。

网络错误很容易被发现。我们的脚本甚至会因错误而停止执行。但是, 如果不运行测试就很难确定库中的重大更改, 而这在生产中是无法做到的。在安装依赖项时, Composer, npm和其他类似工具依赖于语义版本控制-major.minor.patch。如果在composer.json中看到〜1.0.2, 则意味着安装版本1.0.2或最新的修补程序版本, 例如1.0.4。如果看到^ 1.0.2, 则意味着安装版本1.0.2或最新的次要版本或修补程序版本, 例如1.1.0。我们相信, 在引入任何重大更改后, 图书馆供应商会增加主要数量, 但有时会遗漏或不遵守此要求。过去有过这种情况。即使你在composer.json中放入了固定版本, 你的依赖项在composer.json中也可能具有〜和^。

我认为, 如果可以访问, 则更好的方法是使用工件存储库(Nexus, JFrog等)。包含所有必要依赖项的发行版本最初将创建一次。该工件将存储在存储库中, 并从那里获取用于各个测试阶段的信息。同样, 这将是要部署到生产中的构建, 而不是从Git重建应用。

保持代码和数据库兼容

我一眼就爱上Laravel的原因是它的作者如何密切关注细节, 如何考虑开发人员的便利以及如何将许多最佳实践(例如数据库迁移)纳入了框架。

数据库迁移使我们能够同步数据库和代码。它们的两个更改都可以包含在单个提交中, 因此可以包含在单个发布中。但是, 这并不意味着可以在不停机的情况下部署任何更改。在部署过程中的某个时候, 将运行不同版本的应用程序和数据库。如果出现问题, 这一点甚至可能变成一个时期。我们应该始终尝试使它们与同伴的先前版本兼容:旧数据库-新应用程序, 新数据库-旧应用程序。

例如, 假设我们有一个地址列, 需要将其分为地址1和地址2。为了使所有内容兼容, 我们可能需要几个版本。

  1. 在数据库中添加两个新列。
  2. 修改应用程序以尽可能使用新字段。
  3. 将地址数据迁移到新列并将其删除。

这个案例也是一个很好的例子, 说明较小的更改对部署有多大的好处。他们的回滚也更容易。如果我们要在几周或几个月内更改代码库和数据库, 则可能无法停机而无法更新生产系统。

Kubernetes的一些出色之处

即使我们的应用程序规模可能不需要云, 节点和Kubernetes, 我仍然要提到K8中的部署情况。在这种情况下, 我们不对系统进行更改, 而是声明我们要实现的目标以及应该在多少个副本上运行的目标。然后, Kubernetes确保实际状态与所需状态匹配。

每当我们准备好发布新版本时, 我们都会在其中构建带有新文件的映像, 并使用新版本标记该映像, 然后将其传递给K8s。后者将快速提升群集中的图像。它会根据我们提供的准备情况检查在应用程序准备就绪之前等待, 然后明显地将流量重定向到新应用程序并杀死旧应用程序。我们可以很容易地运行应用程序的多个版本, 这使我们仅需执行几个命令即可执行蓝色/绿色或金丝雀部署。

如果你有兴趣, 可以在” Burr Sutter撰写的Kubernetes的9大步骤”中做一些令人印象深刻的演示。

微信公众号
手机浏览(小程序)
0
分享到:
没有账号? 忘记密码?