Test and deploy Laravel applications with GitLab CI/CD and Envoy

Test and deploy Laravel applications with GitLab CI/CD and Envoy

GitLab 通过持续集成为我们的应用程序提供功能,并且可以随时将新的代码更改轻松部署到生产服务器.

在本教程中,我们将向您展示如何初始化应用程序并设置Envoy任务,然后我们将跳入如何使用通过Continuous Delivery测试和部署它的方法.

我们假设您具有 Laravel 和 Linux 服务器的基本经验,并且知道如何使用 GitLab.

Laravel 是一个用 PHP 编写的高质量 Web 框架. 它有一个很棒的社区,提供了 . 除了常规的路由,控制器,请求,响应,视图和(刀片)模板外,Laravel 还提供了许多其他服务,例如缓存,事件,本地化,身份验证以及许多其他服务.

我们将使用Envoy作为基于 PHP 的 SSH 任务运行程序. 它使用简洁的来设置可以在远程服务器上运行的任务,例如,从存储库克隆项目,安装 Composer 依赖项以及运行Artisan 命令 .

Initialize our Laravel app on GitLab

我们假设您已经安装了一个新的 Laravel 项目 ,所以让我们从单元测试开始,并为该项目初始化 Git.

Laravel 的每个新安装(当前为 5.4)都在测试目录中放置了两种类型的测试:”功能”和”单元”. 这是来自的单元测试:

该测试就像断言给定值是 true 一样简单.

Laravel 默认使用PHPUnit进行测试. 如果运行vendor/bin/phpunit我们应该看到绿色输出:

  1. vendor/bin/phpunit
  2. OK (1 test, 1 assertions)

该测试将在以后用于通过 GitLab CI / CD 持续测试我们的应用程序.

Push to GitLab

由于我们已经在本地启动并运行了应用程序,因此现在是将代码库推送到我们的远程存储库的时候了. 让我们在 GitLab 中创建一个名为laravel-sample . 之后,按照项目主页上显示的命令行说明在我们的计算机上启动存储库并推送第一个提交.

  1. cd laravel-sample
  2. git init
  3. git remote add origin git@gitlab.example.com:<USERNAME>/laravel-sample.git
  4. git add .
  5. git commit -m 'Initial Commit'
  6. git push -u origin master

在开始设置 Envoy 和 GitLab CI / CD 之前,让我们快速确保生产服务器已准备好进行部署. 我们已经在 Ubuntu 16.04 上安装了 LEMP 堆栈,该堆栈代表 Linux,NGINX,MySQL 和 PHP.

Create a new user

现在,让我们创建一个新用户,该用户将用于部署我们的网站并使用为其授予所需的权限:

  1. # Create user deployer
  2. sudo adduser deployer
  3. # Give the read-write-execute permissions to deployer user for directory /var/www
  4. sudo setfacl -R -m u:deployer:rwx /var/www

如果您的 Ubuntu 服务器上未安装 ACL,请使用以下命令进行安装:

  1. sudo apt install acl

假设我们要从 GitLab 上的私有存储库将应用程序部署到生产服务器. 首先,我们需要为部署者用户生成一个没有密码短语的新 SSH 密钥对 .

之后,我们需要复制私钥,以使用 SSH 作为部署者用户将其用于连接到我们的服务器,以便能够自动化部署过程:

  1. # As the deployer user on server
  2. #
  3. # Copy the content of public key to authorized_keys
  4. cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
  5. # Copy the private key text block
  6. cat ~/.ssh/id_rsa

现在,让我们将其作为添加到您的 GitLab 项目中. 变量是用户定义的变量,出于安全目的,它们存储在.gitlab-ci.yml . 可以通过导航到项目的设置 > CI / CD来为每个项目添加它们.

KEY字段中,添加名称SSH_PRIVATE_KEY ,然后在VALUE字段中,粘贴您先前复制的私钥. 稍后,我们将在.gitlab-ci.yml使用此变量,以轻松地以部署者用户身份连接到我们的远程服务器,而无需输入其密码.

我们还需要将公共密钥作为添加到” 项目” > “设置” >” 存储库” ,这使我们能够通过SSH 协议从服务器访问存储库.

  1. # As the deployer user on the server
  2. #
  3. # Copy the public key
  4. cat ~/.ssh/id_rsa.pub

在” 标题 “字段中,添加所需的任何名称,然后将公共密钥粘贴到” 密钥”字段中.

现在,让我们在服务器上克隆存储库,以确保deployer用户可以访问该存储库.

  1. # As the deployer user on server
  2. #
  3. git clone git@gitlab.example.com:<USERNAME>/laravel-sample.git

注意:如果询问, 回答是. Are you sure you want to continue connecting (yes/no)? . 它将 GitLab.com 添加到已知主机.

Configuring NGINX

现在,让我们确保我们的 Web 服务器配置指向current/public而不是public .

通过键入以下内容来打开默认的 NGINX 服务器块配置文件:

  1. sudo nano /etc/nginx/sites-available/default

配置应该是这样的.

Setting up Envoy

因此,我们已经准备好生产 Laravel 应用. 接下来是使用 Envoy 执行部署.

要使用 Envoy,我们应该首先按照 Laravel 给出的说明将其安装在本地计算机上.

How Envoy works

Envoy 的优点是它不需要 Blade 引擎,只使用 Blade 语法定义任务. 首先,我们在应用程序的根目录中创建一个Envoy.blade.php ,其中包含一个简单的测试 Envoy 的任务.

  1. @servers(['web' => 'remote_username@remote_host'])
  2. @task('list', ['on' => 'web'])
  3. ls -l
  4. @endtask

如您所料,我们在文件顶部的@servers指令中有一个数组,其中包含一个名为web的键,其值为服务器地址的值(例如, deployer@192.168.1.1 192.168.1.1). 然后,在我们的@task指令中,定义执行任务时应在服务器上运行的 bash 命令.

  1. envoy run list

它应该执行我们之前定义的list任务,该任务连接到服务器并列出目录内容.

Envoy 不是 Laravel 的依赖项,因此您可以将其用于任何 PHP 应用程序.

每次我们部署到生产服务器时,Envoy 都会从 GitLab 存储库下载我们应用程序的最新版本,并将其替换为预览版本. Envoy 做到了这一点,没有任何停机时间 ,因此我们在部署过程中不必担心有人在审查站点. 我们的部署计划是从 GitLab 存储库克隆最新版本,安装 Composer 依赖项,最后激活新版本.

directive

我们部署过程的第一步是在内部定义一组变量 @setup指令. 您可以将app更改为您的应用名称:

  1. ...
  2. @setup
  3. $repository = 'git@gitlab.example.com:<USERNAME>/laravel-sample.git';
  4. $releases_dir = '/var/www/app/releases';
  5. $app_dir = '/var/www/app';
  6. $new_release_dir = $releases_dir .'/'. $release;
  7. @endsetup
  8. ...
  • $repository是我们存储库的地址
  • $releases_dir目录是我们部署应用程序的位置
  • $app_dir是服务器上实时存在的应用程序的实际位置
  • $release contains a date, so every time that we deploy a new release of our app, we get a new folder with the current date as name
  • $new_release_dir是新版本的完整路径,仅用于使任务更整洁

directive

@story指令允许我们定义可以作为单个任务运行的任务列表. 在这里,我们有三个任务,称为clone_repositoryrun_composerupdate_symlinks . 这些变量可用于使我们的任务代码更清晰:

  1. ...
  2. @story('deploy')
  3. clone_repository
  4. run_composer
  5. update_symlinks
  6. @endstory
  7. ...

让我们一一创建这三个任务.

Clone the repository

第一个任务将创建releases目录(如果不存在),然后将存储库的master分支(默认情况下)克隆到由$new_release_dir变量指定的新 release 目录中. releases目录将包含我们所有的部署:

  1. ...
  2. @task('clone_repository')
  3. echo 'Cloning repository'
  4. [ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
  5. git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
  6. cd {{ $new_release_dir }}
  7. git reset --hard {{ $commit }}
  8. @endtask
  9. ...

随着我们项目的发展,它的 Git 历史将随着时间的流逝而持续很长时间. 由于我们为每个版本创建一个目录,因此不必为每个版本下载项目的历史记录. --depth 1选项是一个很好的解决方案, --depth 1可以节省系统时间和磁盘空间.

Installing dependencies with Composer

您可能知道,此任务只是导航到新的发行目录并运行 Composer 来安装应用程序依赖项:

  1. ...
  2. @task('run_composer')
  3. echo "Starting deployment ({{ $release }})"
  4. cd {{ $new_release_dir }}
  5. composer install --prefer-dist --no-scripts -q -o
  6. @endtask
  7. ...

Activate new release

在准备好新版本的要求之后,接下来要做的就是从其中删除存储目录,并创建两个符号链接,以将应用程序的storage目录和文件指向新版本. 然后,我们需要创建另一个符号链接到新版本用的名称current放置在 app 目录. current符号链接始终指向我们应用程序的最新版本:

  1. ...
  2. @task('update_symlinks')
  3. echo "Linking storage directory"
  4. rm -rf {{ $new_release_dir }}/storage
  5. ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
  6. echo 'Linking .env file'
  7. ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
  8. echo 'Linking current release'
  9. ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
  10. @endtask

如您所见,我们使用-nfs作为ln命令的选项,它表示storage.envcurrent不再指向预览的发行版, -nfs通过强制将它们指向新发行版( -nfs f表示强制) ,这是我们进行多个部署的情况.

Full script

脚本已准备就绪,但请确保将deployer@192.168.1.1更改为服务器,并使用要部署应用程序的目录更改/var/www/app app.

最后,我们的Envoy.blade.php文件将如下所示:

  1. @servers(['web' => 'deployer@192.168.1.1'])
  2. @setup
  3. $repository = 'git@gitlab.example.com:<USERNAME>/laravel-sample.git';
  4. $releases_dir = '/var/www/app/releases';
  5. $app_dir = '/var/www/app';
  6. $release = date('YmdHis');
  7. $new_release_dir = $releases_dir .'/'. $release;
  8. @endsetup
  9. @story('deploy')
  10. clone_repository
  11. run_composer
  12. update_symlinks
  13. @endstory
  14. @task('clone_repository')
  15. echo 'Cloning repository'
  16. [ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
  17. git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
  18. cd {{ $new_release_dir }}
  19. git reset --hard {{ $commit }}
  20. @endtask
  21. @task('run_composer')
  22. echo "Starting deployment ({{ $release }})"
  23. cd {{ $new_release_dir }}
  24. composer install --prefer-dist --no-scripts -q -o
  25. @endtask
  26. @task('update_symlinks')
  27. echo "Linking storage directory"
  28. rm -rf {{ $new_release_dir }}/storage
  29. ln -nfs {{ $app_dir }}/storage {{ $new_release_dir }}/storage
  30. echo 'Linking .env file'
  31. ln -nfs {{ $app_dir }}/.env {{ $new_release_dir }}/.env
  32. echo 'Linking current release'
  33. ln -nfs {{ $new_release_dir }} {{ $app_dir }}/current
  34. @endtask

在进行任何部署之前,我们应该做的另一件事是第一次将我们的应用程序storage文件夹手动复制到服务器上的/var/www/app目录. 您可能想要创建另一个 Envoy 任务来为您完成此任务. 我们还在同一路径中创建.env文件, .env设置生产环境变量. 这些是永久性数据,将与每个新版本共享.

现在,我们需要通过运行envoy run deploy来部署我们的应用程序,但这不是必需的,因为 GitLab 可以在 CI 的为我们处理此事,这将在本教程的后面部分进行介绍.

现在是时候提交并将其推送到master分支了. 为了简化起见,我们直接致力于master ,而无需使用功能分支,因为协作不在本教程的讨论范围之内. 在现实世界的项目中,团队可以使用和合并请求在分支之间移动代码:

我们已经在 GitLab 上准备好了我们的应用程序,我们也可以手动部署它. 但是,让我们向前迈出一步,使用” 方法自动完成此操作. 我们需要使用一组自动化测试来检查每个提交,以尽早发现问题,然后,如果我们对测试结果感到满意,则可以将其部署到目标环境.

GitLab CI / CD允许我们使用引擎来处理测试和部署应用程序的过程. 如果您不熟悉 Docker,请参阅如何自动化 Docker 部署 .

为了能够使用 GitLab CI / CD 构建,测试和部署我们的应用程序,我们需要准备工作环境. 为此,我们将使用具有 Laravel 应用程序运行最低要求的 Docker 映像. 可以执行此操作,但是它们可能会使我们的构建运行缓慢,而在使用更快的选项时,这不是我们想要的.

有了 Docker 映像,我们的构建运行得异常快!

Create a Container Image

让我们在应用程序的根目录中创建一个以下内容的Dockerfile

  1. # Set the base image for subsequent instructions
  2. FROM php:7.1
  3. # Update packages
  4. RUN apt-get update
  5. # Install PHP and composer dependencies
  6. RUN apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev
  7. # Clear out the local repository of retrieved package files
  8. RUN apt-get clean
  9. # Install needed extensions
  10. # Here you can install any other extension that you need during the test and deployment process
  11. RUN docker-php-ext-install mcrypt pdo_mysql zip
  12. # Install Composer
  13. RUN curl --silent --show-error https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
  14. # Install Laravel Envoy
  15. RUN composer global require "laravel/envoy=~1.0"

我们添加了 ,该映像由 Debian Jessie 的最低安装和预安装的 PHP 组成,并且非常适合我们的用例.

我们使用了docker-php-ext-install (由官方 PHP Docker 映像提供)来安装所需的 PHP 扩展.

Setting Up GitLab Container Registry

现在我们有了Dockerfile让我们构建并将其推送到GitLab 容器注册表 .

注册表是存储和标记图像以供以后使用的地方. 开发人员可能希望维护自己的注册表,以用于私人,公司映像或仅用于测试的一次性映像. 使用 GitLab 容器注册表意味着您无需设置和管理另一项服务或使用公共注册表.

在您的 GitLab 项目存储库上,导航到” 注册表”选项卡.

您可能需要对项目启用 Container Registry才能看到此选项卡. 您可以在项目的设置>常规>可见性,项目功能,权限下找到它.

要在我们的机器上开始使用 Container Registry,我们首先需要使用我们的 GitLab 用户名和密码登录到 GitLab 注册表:

  1. docker login registry.gitlab.com

然后,我们可以构建图像并将其推送到 GitLab:

  1. docker build -t registry.gitlab.com/<USERNAME>/laravel-sample .
  2. docker push registry.gitlab.com/<USERNAME>/laravel-sample

恭喜你! 您刚刚将第一个 Docker 映像推送到了 GitLab 注册表,如果刷新页面,您应该可以看到它:

注意:您也可以来构建和推送 Docker 映像,而不是在计算机上执行.

我们将在.gitlab-ci.yml配置文件中进一步使用此图像,以处理测试和部署我们的应用程序的过程.

让我们提交Dockerfile文件.

  1. git add Dockerfile
  2. git commit -m 'Add Dockerfile'
  3. git push origin master

为了使用 GitLab CI / CD 构建和测试我们的应用程序,我们需要在存储库的根目录中有一个名为.gitlab-ci.yml的文件. 它类似于 Circle CI 和 Travis CI,但内置 GitLab.

我们的文件将如下所示:

  1. image: registry.gitlab.com/<USERNAME>/laravel-sample:latest
  2. services:
  3. - mysql:5.7
  4. variables:
  5. MYSQL_DATABASE: homestead
  6. MYSQL_ROOT_PASSWORD: secret
  7. DB_HOST: mysql
  8. DB_USERNAME: root
  9. stages:
  10. - test
  11. - deploy
  12. unit_test:
  13. stage: test
  14. script:
  15. - cp .env.example .env
  16. - composer install
  17. - php artisan key:generate
  18. - php artisan migrate
  19. - vendor/bin/phpunit
  20. deploy_production:
  21. stage: deploy
  22. script:
  23. - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
  24. - eval $(ssh-agent -s)
  25. - ssh-add <(echo "$SSH_PRIVATE_KEY")
  26. - mkdir -p ~/.ssh
  27. - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
  28. - ~/.composer/vendor/bin/envoy run deploy --commit="$CI_COMMIT_SHA"
  29. environment:
  30. name: production
  31. url: http://192.168.1.1
  32. when: manual
  33. only:
  34. - master

需要很多东西,不是吗? 让我们逐步进行操作.

Image and Services

运行.gitlab-ci.yml定义的脚本. image关键字告诉跑步者要使用哪个图像. services关键字定义链接到主图像的其他图像 . 在这里,我们将之前创建的容器映像用作主映像,还将 MySQL 5.7 用作服务.

  1. image: registry.gitlab.com/<USERNAME>/laravel-sample:latest
  2. services:
  3. - mysql:5.7
  4. ...

Variables

GitLab CI / CD 允许我们在工作中使用环境变量 . 我们将 MySQL 定义为数据库管理系统,它带有默认创建的超级用户根.

因此,我们应该通过将MYSQL_DATABASE变量定义为数据库名称并将MYSQL_ROOT_PASSWORD变量定义为root的密码来调整 MySQL 实例的配置. 在找到有关 MySQL 变量的更多信息.

还要将变量DB_HOST设置为mysql ,将DB_USERNAMEroot ,这是 Laravel 特定的变量. 我们将DB_HOST定义为mysql而不是127.0.0.1 ,因为我们将 MySQL Docker 映像用作与主 Docker 映像链接的服务.

  1. ...
  2. variables:
  3. MYSQL_DATABASE: homestead
  4. MYSQL_ROOT_PASSWORD: secret
  5. DB_HOST: mysql
  6. DB_USERNAME: root
  7. ...

Unit Test as the first job

我们将所需的外壳程序脚本定义为运行unit_test作业时要执行的脚本变量的数组.

These scripts are some Artisan commands to prepare the Laravel, and, at the end of the script, we’ll run the tests by PHPUnit.

  1. ...
  2. unit_test:
  3. script:
  4. # Install app dependencies
  5. - composer install
  6. # Set up .env
  7. - cp .env.example .env
  8. # Generate an environment key
  9. - php artisan key:generate
  10. # Run migrations
  11. - php artisan migrate
  12. # Run tests
  13. - vendor/bin/phpunit
  14. ...

Deploy to production

deploy_production作业会将应用程序部署到生产服务器. 要使用 Envoy 部署我们的应用程序,我们必须将$SSH_PRIVATE_KEY变量设置为SSH 私钥 . 如果 SSH 密钥已成功添加,我们可以运行 Envoy.

如前所述,GitLab 也支持方法. 环境关键字告诉 GitLab 该作业已部署到production环境. url关键字用于在 GitLab 环境页面上生成指向我们应用程序的链接. only关键字告诉 GitLab CI / CD 只有在管道正在构建master分支时才应执行作业. 最后, when: manual用于将作业从自动运行转变为手动操作.

您可能还需要为添加另一个作业,以便在部署到生产环境之前对应用程序进行最终测试.

Turn on GitLab CI/CD

我们已经准备好使用 GitLab CI / CD 测试和部署应用程序所需的一切. 为此,请提交.gitlab-ci.yml并将其推入master分支. 它将触发管道,您可以在项目的Pipelines下实时观看.

pipelines page

Here we see our Test and Deploy stages. The Test stage has the unit_test build running. click on it to see the Runner’s output.

代码成功通过管道后,我们可以通过单击右侧的播放按钮将其部署到生产服务器.

pipelines page deploy button

部署管道成功通过后,导航至” 管道”>”环境” .

如果某些操作无法正常工作,则可以回滚到应用程序的最新工作版本.

environment page

通过单击右侧指定的外部链接图标,GitLab 将打开生产网站. 我们的部署成功完成,我们可以看到该应用程序正在运行.

如果您想了解部署后生产服务器上的应用程序目录结构如何,这里有三个目录,分别名为currentreleasesstorage . 如您所知, current目录是指向最新版本的符号链接. .env文件包含我们的 Laravel 环境变量.

production server app directory

如果导航到current目录,则应该看到应用程序的内容. 如您所见, .env指向/var/www/app/.env文件, storage也指向目录.

Conclusion

Envoy 也非常适合帮助我们在不编写自定义 bash 脚本和进行 Linux 魔术的情况下部署应用程序.