最近在尝试使用 octane swoole 驱动的 Laravel 项目时出现了一个问题:在更新代码后使用 octane:reload
重新启动 workers
时新代码不生效。
我的项目是通过 deployer [1] 部署的,通过符号链接的形式将项目目录指向新的代码,但 octane:reload
后新代码并没有生效。
之后发现通过符号链接指向新目录、composer
引入新的库、.env
在重启后都不生效,octane::reload
应该是从主进程映射的代码目录中重新启动 worker
进程, 因此新的代码并未生效。
之后我们部署代码后通过执行 octane::stop
和 octane::start
启动服务,这样代码是生效的,但这样并非热部署,每次执行上述命令服务都会中断几秒,导致此时进来的请求 502 报错。
解决方法
参考蓝绿部署形式
对于同一个项目(同一个代码目录)启动两套 octane swoole
服务,通过 nginx 指向新的服务的形式实现 0 downtime
部署。
启动 服务1、服务2 ,首次将 nginx 指向 服务1,在更新代码后通过stop&start
重启服务2,将nginx指向服务2,reload nginx即实现热部署;在下次更新代码后重启服务1,将 nginx 指向 服务1 并 reload。
解决步骤
1.1、修改 octane 启动代码,使 octane 可以通过不同端口启动两套服务
当然你也可以在服务器上部署两套代码,这样就不需要该脚本了
新增 Artisan 命令行,自定义 octane 启动脚本
php artisan make:command Octane/StartOctane
修改脚本:
在原有 octane 服务启动脚本基础上修改不同端口对应不用的状态文件,这样就可以启动多个服务了
<?php
namespace App\Console\Commands\Octane;
use Laravel\Octane\Commands\StartCommand;
use Laravel\Octane\Swoole\ServerStateFile as SwooleServerStateFile;
class StartOctane extends StartCommand
{
/**
* The command's signature.
*
* @var string
*/
public $signature = 'Octane:StartOctane
{--server= : The server that should be used to serve the application}
{--host=127.0.0.1 : The IP address the server should bind to}
{--port= : The port the server should be available on [default: "8000"]}
{--rpc-host= : The RPC IP address the server should bind to}
{--rpc-port= : The RPC port the server should be available on}
{--workers=auto : The number of workers that should be available to handle requests}
{--task-workers=auto : The number of task workers that should be available to handle tasks}
{--max-requests=500 : The number of requests to process before reloading the server}
{--rr-config= : The path to the RoadRunner .rr.yaml file}
{--watch : Automatically reload the server when the application is modified}
{--poll : Use file system polling while watching in order to watch files over a network}';
/**
* The command's description.
*
* @var string
*/
public $description = '无停机部署 Octane server';
/**
* Handle the command.
*
* @return int
*/
public function handle()
{
// 在原有octane 服务启动脚本基础上新增如下代码 start
// 修改不同端口对应不用的状态文件,这样就可以启动多个服务了
$port = $this->option('port') ?: 8012;
app()->bind(SwooleServerStateFile::class, function($app) use ($port){
return new SwooleServerStateFile($app['config']->get(
'octane.state_file',
storage_path('logs/octane-server-state' . $port . '.json')
));
});
// end
$server = $this->option('server') ?: config('octane.server');
return match ($server) {
'swoole' => $this->startSwooleServer(),
'roadrunner' => $this->startRoadRunnerServer(),
default => $this->invalidServer($server),
};
}
}
1.2、服务启动脚本
若我们想启动两套服务,端口分别是 8012、8013
我们的服务使用 supervisorctl 管理:
将 octane:start
换为我们上一步创建的脚本 Octane:StartOctane
[program:server_8012]
command = /bin/php -d variables_order=EGPCS /www/server/current/artisan Octane:StartOctane --max-requests=5000 --workers=4 --task-workers=4 --port=8012
user=www
process_name = %(program_name)s_%(process_num)s
numprocs = 1
autostart = true
autorestart = true
startretries = 1000
stdout_logfile = /log/supervisor/server.log
stdout_logfile_maxbytes = 1000MB
redirect_stderr=true
[program:server_8013]
command = /bin/php -d variables_order=EGPCS /www/server/current/artisan Octane:StartOctane --max-requests=5000 --workers=4 --task-workers=4 --port=8013
user=www
process_name = %(program_name)s_%(process_num)s
numprocs = 1
autostart = true
autorestart = true
startretries = 1000
stdout_logfile = /log/supervisor/server.log
stdout_logfile_maxbytes = 1000MB
redirect_stderr=true
1.3、nginx 配置
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name test.net;
server_tokens off;
root /www/server/current/public;
index index.html index.htm index.php default.html default.htm default.php;
charset utf-8;
error_page 404 /index.php;
location /index.php {
try_files /not_exists @octane;
}
location / {
try_files $uri $uri/ @octane;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)?$
{
expires 12h;
}
location ~ /.well-known {
allow all;
}
location ~ /\.
{
deny all;
}
# 在此处引入配置文件,部署脚本通过修改配置文件设置 nginx 指向的服务
include /www/server/current/nginx_octane.conf;
}
nginx_octane_8012.conf
location @octane {
set $suffix "";
if ($uri = /index.php) {
set $suffix ?$query_string;
}
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header SERVER_PORT $server_port;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://127.0.0.1:8012$suffix;
}
nginx_octane_8013.conf
location @octane {
set $suffix "";
if ($uri = /index.php) {
set $suffix ?$query_string;
}
proxy_http_version 1.1;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header SERVER_PORT $server_port;
proxy_set_header REMOTE_ADDR $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://127.0.0.1:8013$suffix;
}
1.4、服务部署流程
我们是通过 deployer 脚本部署的代码,如上次部署用的 8012 端口的服务,本次部署用 8013 端口服务,服务重启脚本如下:
这样在 8013 端口重启后,在将 nginx 指向8013 即可实现0停机部署
task('supervisor:reload', function(){
$env = get('labels')['env'];
if ($env == 'pre8012’) {
# 重启8012端口服务
run('sudo /usr/bin/supervisorctl restart server_8012:');
# 将 nginx 指向 8012 端口
run('cd {{release_path}} && /usr/bin/cp ./nginx_octane_8012.conf ../../shared/nginx_octane.conf');
}
if ($env == 'pre8013') {
# 重启8013端口服务
run('sudo /usr/bin/supervisorctl restart server_8013:');
# 将 nginx 指向 8013 端口
run('cd {{release_path}} && /usr/bin/cp ./nginx_octane_8013.conf ../../shared/nginx_octane.conf');
}
# 热重启 nginx
run('sudo /usr/bin/lnmp nginx reload');
});
本次执行命令:
dep deploy pre8013 -vvv
引用链接
[1]
deployer : https://deployer.org/