Skip to content

Instantly share code, notes, and snippets.

@WRonX
Last active January 11, 2023 12:54
Show Gist options
  • Save WRonX/b0abbc0dfcc68b8346aa45316219b580 to your computer and use it in GitHub Desktop.
Save WRonX/b0abbc0dfcc68b8346aa45316219b580 to your computer and use it in GitHub Desktop.
Running Laravel queue worker on Azure environment

Introduction

Deploying PHP applications on Azure environments is possible, but makes not much of a sense. Main reason is a number of limitations, especially when you are used to solutions like Heroku.

One of the limitations is the lack of easily achieved background queue workers. Running queue:work as startup command will crash your deployment slot, Azure WebJobs will not work on Linux instances and Windows instances have only PHP 7.something as stack. Your options are limited and this is one of them, very hacky and workaround-like, but worth testing.

⚠️ This is a very poorly tested solution and the stability of it is currently unknown. Use at your own risk.
⚠️ ALSO: this instruction was created for a very specific case, yours is probably different.

Explanation

What we will do, is use supervisor, as mentioned in Laravel Docs. But with Azure it's not as easy as described there. There are few problems:

  1. Azure environment will not preserve any changes made outside /home directory
  2. Laravel Documentation's config example needs to be changed
  3. Azure supervisor doesn't see environment variables and will fail on DB connection attempt

Let's address those:

Proposed solution

Initial setup - queue config

First things first, enable queues just like described in the Laravel Documentation and set the proper QUEUE_CONNECTION in App Service/slot config.

Initial setup - environment variables

Supervisor seems to ignore environment variables that are set from Azure Dashboard level (in App Service/slot configuration). When running artisan queue:work the app behaves like no environment variables are set, therefore it tries to operate on mysql connection that uses localhost and forge credentials (or other, see config/database.php). Obviously this will not work. But what supervisor does NOT ignore is .env file variables. Therefore you have two options:

  1. create .env file once, with proper data
  2. recreate .env file every time container is started

Second option seems to be a bit more time-consuming in terms of preparation, but the advantages are bigger than costs. Mainly: you don't have to change file again, when variables set from dashboard level are changed. You can use this example command I created in a hurry, so don't expect it to be flawlessly written:

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class EnvGenerate extends Command
{
    protected $signature = 'env:generate {--force}';
    protected $description = 'Generates .env file';

    private $default_vars = [
        'APP_ENV',
        'APP_DEBUG',
        'APP_KEY',
        'DATABASE_URL',
        'DB_CONNECTION',
        'QUEUE_CONNECTION'
    ];

    public function handle(): int
    {
        if (!$this->option('force')) {
            $this->error('This will reset your .env file. No action will be performed without --force option');
            return self::FAILURE;
        }

        $vars = [];

        if (getenv('SUPERVISOR_ENV_VARS')) {
            $vars = explode(',', getenv('SUPERVISOR_ENV_VARS'));
        }
        if (!count($vars)) {
            $vars = $this->default_vars;
        }

        $env_content = '';

        foreach ($vars as $v) {
            $val = getenv($v);
            if (!is_numeric($val)) {
                $val = '"' . $val . '"';
            }
            $env_content .= $v . '=' . $val . PHP_EOL;
        }

        if (file_put_contents('.env', $env_content) === false) {
            return self::FAILURE;
        }

        return self::SUCCESS;
    }
}

Just add the following at the end of your Startup Command:

&& php artisan env:generate --force

Notice that you can set which env variables will be saved in .env file by setting SUPERVISOR_ENV_VARS variable to something like

APP_ENV,DATABASE_URL,DB_CONNECTION

Supervisor configuration

Add a laravel-worker.conf to your project in the main directory. Example working content:

[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/site/wwwroot/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=root
numprocs=8
redirect_stderr=true
stdout_logfile=/home/site/wwwroot/storage/logs/worker.log
stopwaitsecs=3600

Changes made, compared to Laravel Docs Example:

  • artisan path in command was changed to Azure-specific
  • command arguments changed by removing sqs option (we're not using SQS here)
  • log file path changed as well
  • supervisor execution username changed

Supervisor installation and App Service config

This one is similar to Laravel Docs, although there are some differences, so long story short - this is what you need to add at the end of your deployment slot's Startup Command (Configuration->General):

&& apt install supervisor -y && cp /home/site/wwwroot/laravel-worker.conf /etc/supervisor/conf.d && service supervisor start && supervisorctl reread && supervisorctl update && supervisorctl start laravel-worker:*

If we don't use Docker images here, we will have to install supervisor with every container startup. Fortunately that doesn't take long.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment