Skip to content

Instantly share code, notes, and snippets.

@joecampo
Forked from pascalbaljet/SwapOctaneServer.php
Created December 2, 2022 22:33
Show Gist options
  • Save joecampo/b79729dce027ce87ad37a5bc9d239ac1 to your computer and use it in GitHub Desktop.
Save joecampo/b79729dce027ce87ad37a5bc9d239ac1 to your computer and use it in GitHub Desktop.
Blue-green deployment with Laravel Octane
export OCTANE_STATE_FILE=/home/forge/website.com/shared/storage/logs/octane-server-state-blue.json
php8.0 artisan octane:start --port=8001
export OCTANE_STATE_FILE=/home/forge/website.com/shared/storage/logs/octane-server-state-green.json
php8.0 artisan octane:start --port=8002
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Console\Concerns\CallsCommands;
use Illuminate\Support\Facades\Http;
use Laravel\Forge\Forge;
use Laravel\Forge\Resources\Daemon;
class SwapOctaneServer extends Command
{
use CallsCommands;
private Forge $forgeApi;
private $serverId;
private $siteId;
private $bluePort;
private $greenPort;
const BLUE = 'blue';
const GREEN = 'green';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'octane:swap-server';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Start a new Octane daemon and swap the port in the nginx config';
/**
* Execute the console command.
*
* @return int
*/
public function handle(Forge $forge)
{
$this->forgeApi = $forge;
$this->serverId = config('services.forge.server_id');
$this->siteId = config('services.forge.site_id');
$this->bluePort = config('services.forge.blue_port'); // 8001
$this->greenPort = config('services.forge.green_port'); // 8002
$nginxPort = $this->getNginxPort();
$this->info('Current nginx port: ' . $nginxPort);
$blueIsActive = $nginxPort === $this->bluePort;
$this->start($blueIsActive ? static::GREEN : static::BLUE);
$this->stop($blueIsActive ? static::BLUE : static::GREEN);
}
private function getNginxPort(): int
{
$config = $this->forgeApi->siteNginxFile($this->serverId, $this->siteId);
preg_match('/(proxy_pass http:\/\/127.0.0.1:)(\d+)\$/', $config, $matches);
return $matches[2];
}
private function start($server)
{
$this->info('Start server: ' . $server);
// make sure there's no dangling daemon
$this->stop($server);
$this->forgeApi->createDaemon($this->serverId, [
"command" => "bash start-{$server}-server.sh",
"user" => "forge",
"directory" => "/home/forge/website.com/current", // symbolic link
]);
$port = $server === static::BLUE ? $this->bluePort : $this->greenPort;
$this->info('Polling server on port: ' . $server);
retry(
300,
fn () => Http::timeout(1)
->connectTimeout(1)
->get("127.0.0.1:{$port}"),
100
);
$this->updateNginxConfig($port);
}
private function updateNginxConfig($newPort)
{
$this->info('Updating nginx config to port: ' . $newPort);
$config = $this->forgeApi->siteNginxFile($this->serverId, $this->siteId);
$config = preg_replace_callback('/(proxy_pass http:\/\/127.0.0.1:)(\d+)\$/', function ($matches) use ($newPort) {
return $matches[1] . $newPort . '$';
}, $config);
$this->forgeApi->updateSiteNginxFile($this->serverId, $this->siteId, $config);
}
private function stop($server)
{
$forge = $this->forgeApi;
/** @var Daemon $daemon */
$daemon = collect($forge->daemons($this->serverId))->firstWhere('command', "bash start-{$server}-server.sh");
if ($daemon) {
$this->info('Wait for pending requests on server: ' . $server);
usleep(config('octane.max_execution_time') * 1000 * 1000);
$this->info('Stop server: ' . $server);
rescue(fn () => $forge->deleteDaemon($daemon->serverId, $daemon->id));
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment