Questa guida mostra come avviare, fermare, riavviare, descrivere e creare istanze EC2 da un’app Laravel usando l’AWS SDK for PHP v3. Gli esempi richiedono PHP 8.1+, Laravel 10+ e credenziali configurate tramite .env o profili locali.
Prerequisiti
- Account AWS con permessi su EC2 (meglio via IAM Role o utente con principi di least privilege).
- PHP 8.1+ e Laravel installati.
- Variabili d’ambiente per le credenziali:
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_REGION
.
Esempio di policy IAM minima per gestione base
{
"Version": "2012-10-17",
"Statement": [
{ "Effect": "Allow", "Action": [
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:RebootInstances"
], "Resource": "*" }
]
}
Per creare/terminare istanze aggiungi: ec2:RunInstances
, ec2:TerminateInstances
, ec2:CreateTags
e i permessi per key pair e security group.
Installazione pacchetti
composer require aws/aws-sdk-php
Configurazione variabili d’ambiente
cp .env .env.example.backup
AWS_ACCESS_KEY_ID=<la_tua_access_key>
AWS_SECRET_ACCESS_KEY=<la_tua_secret_key>
AWS_REGION=eu-central-1
AWS_EC2_DEFAULT_INSTANCE=i-0123456789abcdef0
Creare un service EC2 riutilizzabile
php artisan make:directory App/Services
# oppure crea la cartella manualmente
<?php
// app/Services/Ec2Service.php
namespace App\Services;
use Aws\Ec2\Ec2Client;
use Aws\Exception\AwsException;
class Ec2Service
{
public function __construct(
private ?Ec2Client $client = null
) {
$this->client = $client ?: new Ec2Client([
'version' => 'latest',
'region' => config('app.aws_region', env('AWS_REGION', 'eu-central-1')),
'credentials' => [
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
],
]);
}
public function describe(string $instanceId): array
{
$res = $this->client->describeInstances([
'InstanceIds' => [$instanceId],
]);
$inst = $res['Reservations'][0]['Instances'][0] ?? null;
if (!$inst) {
throw new RuntimeException('Istanza non trovata');
}
return [
'id' => $inst['InstanceId'] ?? null,
'state' => $inst['State']['Name'] ?? null,
'type' => $inst['InstanceType'] ?? null,
'az' => $inst['Placement']['AvailabilityZone'] ?? null,
'publicIp' => $inst['PublicIpAddress'] ?? null,
'privateIp' => $inst['PrivateIpAddress'] ?? null,
'launchTime' => (string)($inst['LaunchTime'] ?? ''),
];
}
public function start(string $instanceId): void
{
$this->client->startInstances(['InstanceIds' => [$instanceId]]);
}
public function stop(string $instanceId, bool $hibernate = false): void
{
$this->client->stopInstances([
'InstanceIds' => [$instanceId],
'Hibernate' => $hibernate,
]);
}
public function reboot(string $instanceId): void
{
$this->client->rebootInstances(['InstanceIds' => [$instanceId]]);
}
public function statusChecks(string $instanceId): array
{
$res = $this->client->describeInstanceStatus([
'InstanceIds' => [$instanceId],
'IncludeAllInstances' => true,
]);
return $res['InstanceStatuses'][0] ?? [];
}
// Waiters semplici via polling
public function waitRunning(string $instanceId, int $timeout = 300): void
{
$this->waitForState($instanceId, 'running', $timeout);
}
public function waitStopped(string $instanceId, int $timeout = 300): void
{
$this->waitForState($instanceId, 'stopped', $timeout);
}
private function waitForState(string $instanceId, string $target, int $timeout): void
{
$deadline = time() + $timeout;
do {
$state = ($this->describe($instanceId)['state']) ?? null;
if ($state === $target) return;
usleep(5_000_00); // 0.5s
} while (time() < $deadline);
throw new RuntimeException("Timeout in attesa di stato {$target}");
}
public function create(array $params): string
{
$defaults = [
'InstanceType' => 't3.micro',
'MinCount' => 1,
'MaxCount' => 1,
];
$run = $this->client->runInstances(array_merge($defaults, $params));
$instanceId = $run['Instances'][0]['InstanceId'] ?? null;
if (!$instanceId) {
throw new RuntimeException('Creazione istanza fallita');
}
return $instanceId;
}
```
}
Comandi Artisan per orchestrare EC2
Describe / Start / Stop / Reboot
php artisan make:command Ec2Describe
php artisan make:command Ec2Start
php artisan make:command Ec2Stop
php artisan make:command Ec2Reboot
<?php
// app/Console/Commands/Ec2Describe.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\Ec2Service;
class Ec2Describe extends Command
{
protected $signature = 'ec2:describe {instanceId?}';
protected $description = 'Mostra dettagli di un\'istanza EC2';
public function handle(Ec2Service $ec2): int
{
$id = $this->argument('instanceId') ?: env('AWS_EC2_DEFAULT_INSTANCE');
$info = $ec2->describe($id);
$this->table(array_keys($info), [array_values($info)]);
return self::SUCCESS;
}
}
<?php
// app/Console/Commands/Ec2Start.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\Ec2Service;
class Ec2Start extends Command
{
protected $signature = 'ec2:start {instanceId?} {--wait}';
protected $description = 'Avvia un\'istanza EC2';
public function handle(Ec2Service $ec2): int
{
$id = $this->argument('instanceId') ?: env('AWS_EC2_DEFAULT_INSTANCE');
$ec2->start($id);
$this->info("Start inviato a {$id}");
if ($this->option('wait')) {
$ec2->waitRunning($id);
$this->info('Istanza in stato running');
}
return self::SUCCESS;
}
}
<?php
// app/Console/Commands/Ec2Stop.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\Ec2Service;
class Ec2Stop extends Command
{
protected $signature = 'ec2:stop {instanceId?} {--hibernate} {--wait}';
protected $description = 'Ferma un\'istanza EC2';
public function handle(Ec2Service $ec2): int
{
$id = $this->argument('instanceId') ?: env('AWS_EC2_DEFAULT_INSTANCE');
$ec2->stop($id, (bool)$this->option('hibernate'));
$this->info("Stop inviato a {$id}");
if ($this->option('wait')) {
$ec2->waitStopped($id);
$this->info('Istanza in stato stopped');
}
return self::SUCCESS;
}
}
<?php
// app/Console/Commands/Ec2Reboot.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\Ec2Service;
class Ec2Reboot extends Command
{
protected $signature = 'ec2:reboot {instanceId?} {--wait}';
protected $description = 'Riavvia un\'istanza EC2';
public function handle(Ec2Service $ec2): int
{
$id = $this->argument('instanceId') ?: env('AWS_EC2_DEFAULT_INSTANCE');
$ec2->reboot($id);
$this->info("Reboot inviato a {$id}");
if ($this->option('wait')) {
$ec2->waitRunning($id);
$this->info('Istanza di nuovo running');
}
return self::SUCCESS;
}
}
Creare una nuova istanza EC2
Per creare un’istanza servono AMI, tipo istanza, key pair (SSH), security group e subnet. Esempio con tag e user data per installare Nginx su Amazon Linux 2023.
<?php
// app/Console/Commands/Ec2Create.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Services\Ec2Service;
use Aws\Ec2\Ec2Client;
class Ec2Create extends Command
{
protected $signature = 'ec2:create
{--image-id= : AMI da usare (se omessa, passa quella giusta per la tua regione)}
{--type=t3.micro : Istanza}
{--key= : KeyPair}
{--sg=* : Security Group IDs}
{--subnet= : Subnet ID}
{--tag=* : Tag in formato Key=Value}
{--wait : Attendi stato running}';
protected $description = 'Crea una nuova istanza EC2';
public function handle(Ec2Service $ec2): int
{
$imageId = $this->option('image-id');
if (!$imageId) {
$this->error('Specifica --image-id per evitare ambiguità.');
return self::INVALID;
}
$tags = [];
foreach ((array)$this->option('tag') as $t) {
[$k, $v] = array_pad(explode('=', $t, 2), 2, '');
if ($k !== '') $tags[] = ['Key' => $k, 'Value' => $v];
}
$userData = base64_encode(<<<'BASH'
#!/bin/bash
dnf -y update
dnf -y install nginx
systemctl enable nginx
systemctl start nginx
BASH);
$params = [
'ImageId' => $imageId,
'InstanceType' => $this->option('type'),
'KeyName' => $this->option('key'),
'SecurityGroupIds' => (array)$this->option('sg'),
'SubnetId' => $this->option('subnet'),
'MinCount' => 1,
'MaxCount' => 1,
'UserData' => $userData,
'TagSpecifications' => [[
'ResourceType' => 'instance',
'Tags' => $tags ?: [['Key' => 'Project','Value' => 'laravel-ec2']]
]]
];
$id = $ec2->create($params);
$this->info("Nuova istanza: {$id}");
if ($this->option('wait')) {
$ec2->waitRunning($id);
$this->info('Istanza running');
}
return self::SUCCESS;
}
}
Esempi d’uso da CLI
# Descrivere
php artisan ec2:describe i-0123456789abcdef0
# Avviare e attendere running
php artisan ec2:start i-0123456789abcdef0 --wait
# Fermare con ibernazione e attendere stopped
php artisan ec2:stop i-0123456789abcdef0 --hibernate --wait
# Riavviare e attendere running
php artisan ec2:reboot i-0123456789abcdef0 --wait
# Creare una nuova istanza (parametri di esempio)
php artisan ec2:create
\--image-id ami-0abcdef1234567890
\--type t3.micro
\--key my-key
\--sg sg-0123abcd
\--subnet subnet-0a1b2c3d
\--tag Project=laravel-ec2 --tag Env=dev
\--wait
Gestione sicura di credenziali e ruoli
- Preferisci IAM Role associate a runner o a EC2/Lambda rispetto a chiavi statiche.
- In locale usa .env e non committare mai le chiavi. Valuta l’uso di secret manager.
- Separa permessi di sola lettura (describe) da quelli di ciclo di vita (start/stop/run/reboot).
Best practice operative
- Idempotenza: controlla lo stato prima di chiamare
StartInstances
/StopInstances
. - Retry e backoff: in caso di throttling applica retry esponenziale; per batch usa code/queue Laravel.
- Attese affidabili: usa i metodi wait (o polling) prima di step successivi come tagging o health check.
- Tagging: imposta tag coerenti per cost allocation (
Project
,Env
,Owner
). - Sicurezza di rete: limita i Security Group (SSH solo dal tuo IP, HTTP/HTTPS secondo necessità).
- Spegnimento programmato: crona comandi Artisan (Scheduler) per spegnere le istanze inattive e ridurre i costi.
Scheduler Laravel: stop notturno di istanze
<?php
// app/Console/Kernel.php (estratto)
protected function schedule(\Illuminate\Console\Scheduling\Schedule $schedule): void
{
$schedule->command('ec2:stop --wait')
->dailyAt('23:30')
->environments(['production'])
->withoutOverlapping();
}
Diagnostica rapida (status checks)
<?php
// uso rapido da Tinker
// php artisan tinker
// >>> app(\App\Services\Ec2Service::class)->statusChecks(env('AWS_EC2_DEFAULT_INSTANCE'));
Conclusione
Con un service dedicato e pochi comandi Artisan, Laravel può orchestrare l’intero ciclo di vita di istanze EC2: dalla creazione con user data al controllo dello stato, fino allo stop programmato. Adatta gli esempi a policy IAM, networking e osservabilità del tuo ambiente.