file

1. 前言

开发完项目,免不了要部署上线。纯手动操作,登录、拉代码、改配置、清缓存,各种服务重启一条龙下来,人生宝贵的时间就又花费了不少。而且手动操作十分容易出错,遗漏部分步骤都有可能产生一些邪门问题。倘若有多台服务器,上述问题还会加剧,呜呼哀哉!常言道,人生苦短,生活中比代码有意思的事情多了去了,所以我很早就开始寻求一种能轻松部署 Laravel 项目的办法。

Laravel 的官方文档里介绍了 Envoy,能满足大部分场景,但仍然有一些限制。直到后来看到了 Deployer,大有相见恨晚之感。

2. 介绍

在开始之前,有必要了解一下 Deployer。Deployer 是一个基于 SSH 协议的无侵入 Web 项目部署工具,因为它不需要你在服务器上装服务之类的东西就可以使用,只需要在你的开发机,或者笔记本,就是发起部署动作的一方安装即可。

它的原理就是通过 SSH 在服务器上创建目录,移动文件,执行指定的动作来完成项目的部署。

主要优势

  • 真正解放双手,一条命令完成部署;
  • 零宕机时间,在部署过程中,项目仍然能够正常访问。部署成功后,才切换到新的版本;
  • 可以很方便地进行回滚;
  • 丰富的任务钩子和预置任务,可灵活组合完成各种任务,比如安装前端依赖和编译等;
  • 其它黑科技等你发掘……

我画了一张图来说明它的操作原理:

file

绘图工具:https://www.draw.io/

简单介绍一下,Deployer 安装在本地,它通过 SSH 协议登录到服务器 Web 服务器上执行一系列我们预定的操作,其中包含从代码库 Git 服务器拉取项目代码部署到 Web 服务器指定的目录完成部署。

一共分为以下几个部分:

  • 在本地安装 Deployer;
  • 部署 Web 项目。

我们分开一个个讲,在后面的每一步请注意区分当前逻辑所在的环境:本地/服务器。

3. 安装

3.1 本地安装 Deployer

此部分在本地操作。

安装 Deployer,在项目目录下运行如下代码即可:

composer require --dev deployer/deployer

安装完成后应该可以使用以下命令来查看它的版本信息:

dep --version
# Deployer 7.1.1

如果提示 dep 命令不存在的话,将如下 alias 添加到 .bashrc.zshrc 文件:

alias dep='vendor/bin/dep'

然后开一个新的终端窗口,应该就有 dep 命令了。

Deployer 的安装就完成了。

3.2 部署项目

这些都在本地操作哦~

假设我们的项目在本地 /www/demo-project 下,那么我们切换到该目录:

cd /www/demo-project

然后执行 Deployer 的初始化命令:

dep init

图片标题

它会让你选择项目类型,比如 Laravel,Symfony 等,如果你都不是,选择 Common 类型即可。我们选择 Laravel。

这一步操作将会在当前目录生成一个 deploy.php 文件,这个文件就是部署清单,也就是告诉 Deployer 怎样去部署你的项目。初始配置如下:

<?php
namespace Deployer;

require 'recipe/laravel.php';

// Config

set('repository', 'git@gitee.com:ilaoniu/demo');

add('shared_files', []);
add('shared_dirs', []);
add('writable_dirs', []);

// Hosts

// Hooks

after('deploy:failed', 'deploy:unlock');

修改后的配置如下:

<?php
namespace Deployer;

require 'recipe/laravel.php';

// Config
set('repository', 'git@gitee.com:ilaoniu/demo');

add('shared_files', []);
add('shared_dirs', []);
add('writable_dirs', []);

// 拷贝 vendor 目录加速安装,避免每次都重新安装
add('copy_dirs', ['vendor']);

// Hosts
host('demo.ilaoniu.cn')
    ->set('labels', ['env' => 'prod'])
    ->set('remote_user', 'root')
    ->set('become', 'deployer')
    ->setIdentityFile('~/.ssh/ilaoniu-root')
    ->set('branch', 'main')
    ->set('deploy_path', '/home/wwwroot/demo.ilaoniu.cn');

// Hooks
after('deploy:failed', 'deploy:unlock');

// 安装依赖
desc('pnpm install');
task('deploy:pnpm:install', function () {
    run('cd {{release_path}} && pnpm install', ['timeout' => 600]);
});

// 编译前端资源
desc('pnpm build');
task('deploy:pnpm:build', function () {
    run('cd {{release_path}} && pnpm build', ['timeout' => 240]);
});

// 定义一个上传 .env 文件的任务
desc('Upload .env file');
task('env:upload', function() {
    // 将本地的 .env 文件上传到代码目录的 .env
    upload('.env', '{{release_path}}/.env');
});

before('deploy:vendors', 'deploy:copy_dirs');
after('deploy:vendors', 'deploy:pnpm:install');
after('deploy:pnpm:install', 'deploy:pnpm:build');

正确填写完配置清单以后,我们就可以部署我们的项目了,确认你的代码已经提交到代码仓库,因为执行部署的时候并不是将当前代码部署到服务器,而是从代码库拉最新的版本。

然后在当前目录执行:

dep deploy env=prod // 部署到生产环境

就可以看到整个部署过程了,一般正常会是像下面这样子:

ilaoniu@ilaonius-MacBook-Pro demo % dep deploy env=prod
task deploy:info
[demo.ilaoniu.cn] info deploying main
task deploy:setup
task deploy:lock
task deploy:release
task deploy:update_code
task deploy:shared
task deploy:writable
task deploy:copy_dirs
task deploy:vendors
task deploy:pnpm:install
task deploy:pnpm:build
task artisan:storage:link
task artisan:config:cache
task artisan:route:cache
task artisan:view:cache
task artisan:event:cache
task artisan:migrate
task deploy:symlink
task deploy:unlock
task deploy:cleanup
task deploy:success
[demo.ilaoniu.cn] info successfully deployed!

可以在命令后 -vvv 命令执行的显示详细信息。

如果失败的话就需要检查一下哪一步出错了,通常根据报错信息即可定位。

首次部署后还需要设置 .env,并生成应用密钥,修改 Nginx 网站配置。

4. 目录结构

Deployer 部署完成后,在服务器上的结构会是这样子:

drwxr-xr-x  5 www  www  4096 Jan 16 07:47 .
drwxr-xr-x  6 www  www  4096 Aug  3 13:26 ..
lrwxrwxrwx  1 www  www    12 Jan 16 07:46 current -> releases/155
drwxrwxr-x  3 www  www  4096 Jan 16 07:47 .dep
drwxrwxr-x 12 www  www  4096 Jan 16 07:47 releases
drwxrwxr-x  3 www  www  4096 Aug  3 13:25 shared

其中,.dep 为 Deployer 的一些版本信息,不用去研究,我们需要关心的是下面这几个:

  • current - 它是指向一个具体的版本的软链接,你的 Nginx 配置中 root 应该指向它,比如 Laravel 项目的话 root 就指向:/home/wwwroot/demo.ilaoniu.cn/current/public,同时 SSL 证书配置 /usr/local/nginx/conf/ssl/demo.ilaoniu.cn/demo.ilaoniu.cn.confLe_Webroot 也要相应更改为 /home/wwwroot/demo.ilaoniu.cn/current/public,不然自动续期证书时会失败。
  • releases - 部署的历史版本文件夹,里面可能有很多个最近部署的版本,可以根据你的配置来设置保留多少个版本,建议 5 个。保留版本可以让我们在上线出问题时使用 dep rollback 快速回滚项目到上一个版本。
  • shared - 共享文件夹,它的作用就是存储我们项目中版本间共享的文件,比如 Laravel 项目的 .env 文件,storage 目录,或者你项目的上传文件夹,它会以软链接的形式链接到当前版本中。

OK,基本上这样子就完成了整体 Deployer 需要考虑的地方以及使用细节了,相信大部分同学的问题都出在权限问题上。

5. 注意事项

如果使用 Horizon,并使用 Supervisor 来监控进程,需要在 Supervisor 配置文件中将运行 Horizon 的用户指定为 deployer(我们使用 deployer 用户连接服务器部署代码并重启 Horizon 进程,权限要一致),不然会重启失败,这部分内容我们将在下一章节说明。