Envoy 任务运行器 Blade 语法

解读

在国内一线/二线互联网公司的 PHP 后端面试中,Laravel 生态是高频考点。Envoy 作为 Laravel 官方出品的轻量级部署脚本工具,其 Blade 语法常被用来考察候选人对「服务器编排」与「模板渲染」双重概念的理解深度。面试官通常不会只问“怎么用”,而是通过“写一段 Envoy 脚本”或“这段脚本会输出什么”来验证你是否真正明白 Blade 在 CLI 场景下的编译机制、变量逃逸规则以及与服务端 SSH 指令的交互边界。答不上 Blade 语法细节,会被直接判定为“只会 Composer 拉包,不会写部署”。

知识点

  1. Envoy 文件本质:Blade 模板(*.blade.php),由 illuminate/view 组件编译,运行期变量来自 @setup@story@task 及命令行 --var=xxx
  2. 指令分类
    2.1 结构指令:@task@setup@story@slack@error@success@finished
    2.2 流程控制:@if@foreach@for@while@unless
    2.3 输出控制:{{ }} 原生 echo、{!! !!} 不转义、{{{ }}} Laravel 5 以前三重转义(现在已废弃,面试里可能故意挖坑)
    2.4 注释:{{-- --}} 在编译阶段直接丢弃,不会出现在最终 bash 脚本里
  3. 变量作用域:
    • @setup 里用 PHP 定义:$foo = 'bar';
    • 命令行注入:envoy run deploy --branch=main 在模板里用 $branch 直接读取
  4. 编译产物:Envoy 把 Blade 编译成纯 PHP 后再 eval(),最终输出 bash 文本,通过 SSH 在远端执行;因此 PHP 变量必须解析完毕,不能出现“未定义”残留。
  5. 常见坑:
    • {{ }} 里写 date('Y-m-d') 会被转义,导致远端 shell 收到 2019-07-18 而不是 $() 命令替换;正确写法是 @php echo date('Y-m-d'); @endphp 或直接在 @setup 里算好。
    • 使用 @if($env === 'prod') 时,$env 必须是 PHP 变量,不能写 bash 变量 $env,否则编译期就报错。
  6. 国内镜像加速:在 @setup 里写 putenv('COMPOSER_REPO_PACKAGIST=composer.aliyun.com'); 可提升 Composer 安装速度,面试时主动提及是加分项。
  7. 回滚策略:利用 @storydeployrollback 做成两个任务,结合 releases/{$timestamp} 软链,是国内灰度发布的通用方案;能现场写出模板骨架可直通二面。

答案

下面给出一段可直接运行的 Envoy.blade.php,覆盖“变量注入、条件分支、循环、注释、输出转义、回滚”六个考点,并在关键行用中文注释说明 Blade 语法要点。面试官若让你“解释每一行”,按注释回答即可。

{{-- 1. 注释:这行不会出现在最终 bash 脚本里 --}}
@setup
    // PHP 代码段,定义变量
    $user = 'webop';              // 远端 SSH 用户
    $base = '/data/webroot';      // 项目根目录
    $repo = 'git@gitlab.xxx.cn:group/project.git';
    $branch = $branch ?? 'master'; // 允许命令行 --branch=xxx 覆盖
    $keep  = 5;                    // 保留最近 5 个版本
    $now   = date('YmdHis');       // 时间戳目录
@endsetup

@story('deploy')
    clone
    composer
    link
    clean
    reload
@endstory

@task('clone', ['on' => 'web'])
    echo '==== 拉取代码 {{ $now }} ===='
    cd {{ $base }}/releases
    git clone {{ $repo }} {{ $now }}
    cd {{ $now }}
    git checkout {{ $branch }}
@endtask

@task('composer', ['on' => 'web'])
    echo '==== 安装依赖 ===='
    cd {{ $base }}/releases/{{ $now }}
    {{-- 使用国内镜像加速,面试加分项 --}}
    composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
    composer install --no-dev -q
@endtask

@task('link', ['on' => 'web'])
    echo '==== 切换软链 ===='
    ln -nfs {{ $base }}/releases/{{ $now }} {{ $base }}/current
@endtask

@task('clean', ['on' => 'web'])
    echo '==== 清理旧版本 ===='
    cd {{ $base }}/releases
    {{-- Blade 的 foreach 在编译期展开,生成纯 bash --}}
    @foreach (range(1, $keep) as $skip)
        # 保留最近 {{ $keep }} 个目录,其余 rm -rf
    @endforeach
    ls -t | tail -n +{{ $keep + 1 }} | xargs rm -rf
@endtask

@task('reload', ['on' => 'web'])
    echo '==== 平滑重启 ===='
    sudo -u {{ $user }} php-fpm -t && sudo -u {{ $user }} service php-fpm reload
@endtask

{{-- 回滚任务:直接指向上一个目录 --}}
@task('rollback', ['on' => 'web'])
    echo '==== 回滚到上一个版本 ===='
    cd {{ $base }}/releases
    last=$(ls -t | head -n 2 | tail -n 1)
    ln -nfs {{ $base }}/releases/$last {{ $base }}/current
    sudo -u {{ $user }} service php-fpm reload
@endtask

@finished
    @if ($task === 'deploy')
        @slack('hook-url', '#ops', "部署成功:{{ $branch }} - {{ $now }}")
    @endif
@endfinished

使用示例

envoy run deploy --branch=main
envoy run rollback

拓展思考

  1. 多机并发:把 @servers 声明为数组,利用 @parallelclonecomposer 在 10 台机器上同时执行,面试可延伸问“如何防止并发写同一缓存目录”。
  2. 灰度验证:在 @story 里插入 healthcheck 任务,调用内网接口 https://gray.xxx.cn/api/health,超时 5 秒则自动 @slack 告警并触发 rollback,体现你对 SRE 理念的落地。
  3. 秘钥安全:国内企业对“把私钥写进 Envoy”零容忍,正确姿势是把 .envoy/id_rsa 放到 CI 的 Secret 管理(如阿里云 KMS、腾讯云 SSM),模板里只写 {{ $privateKey }},并设置文件权限 600,面试主动提到可展示安全意识。
  4. 与 Kubernetes 对比:Envoy 适合“少量云主机 + 传统 LNMP”场景,如果公司全面 K8s,可用 Helm 或 ArgoCD 替代;面试官若追问“为什么不用 Jenkins + Ansible”,可从“ Envoy 无守护进程、学习成本低、与 Laravel 生态无缝”三点回应,体现技术选型思辨。