Technology
Dynamic Management of OVH Instances in Laravel PHP
In our sale events platform, Auctibles, we use the OVH public cloud to run a dynamic set of compute instances running the LiveKit WebRTC video server. We vary the number of instances based on the number of video-streaming sale events.
As our platform is built using the Laravel PHP framework, we use the php-ovh SDK to manage instances.
We define an OVH
service where all interaction with the OVH public cloud will occur.
#The Service
use \Ovh\Api;
class OVH
{
private $ovh;
private $flavor; // aka VM type based on size
private $image_name;
private $datacenter_region_name;
public function __construct()
{
$this->flavor = config('ovh.flavor');
$this->image_name = config('ovh.image_name');
$this->datacenter_region_name = config('ovh.region');
$this->ovh = new Api(
config('ovh.OVH_APPLICATION_KEY'),
config('ovh.OVH_APPLICATION_SECRET'),
config('ovh.OVH_ENDPOINT'),
config('ovh.OVH_CONSUMER_KEY')
);
}
}
After creating an instance, we launch a job to monitor progress:
WaitInstance::dispatch($instance_id, 'started');
#Create Instance
A service is a project in the OVH public cloud, the network is the public Internet, and the instance name is a random string that our code generates.
public function create_instance($service_name, $network_id, $instance_name)
{
try {
$user_data_str = $this->fill_cloud_init(config('ovh.OVH_USER_DATA_FILE_NAME'));
// get all images for our region for this particular flavor
$result = $this->ovh->get("/cloud/project/$service_name/image", array(
'flavorType' => $this->flavor,
'osType' => 'linux',
'region' => $this->datacenter_region_name,
));
$image_name = $this->image_name;
$images = collect($result)->filter(function($image) use($image_name) {
return $image['name'] == $image_name;
});
// Ubunto image to create
$ubuntu_image = $images->first();
$server_config = [
'flavorId' => $this->flavor,
'imageId' => $ubuntu_image['id'],
'sshKeyId' => config('ovh.OVH_SSH_KEY_ID'),
'name' => $instance_name,
'region' => $this->datacenter_region_name,
'networks' => [
[
// connect the server to our private network for the project
'networkId' => config('ovh.OVH_PRIVATE_NETWORK_ID'),
],
[
'networkId' => $network_id,
]
],
'userData' => $user_data_str,
];
$result = $this->ovh->post("/cloud/project/$service_name/instance",
$server_config
);
return $result;
} catch (GuzzleHttp\Exception\ClientException $e) {
$response = $e->getResponse();
$responseBodyAsString = $response->getBody()->getContents();
throw new Exception("ClientExcepion cannot create instance $responseBodyAsString");
} catch(GuzzleHttp\Exception\ServerException $e) {
$response = $e->getResponse();
$responseBodyAsString = $response->getBody()->getContents();
throw new Exception("ServerExcepion cannot create instance $responseBodyAsString");
} catch(Exception $e) {
$message = $e->getMessage();
throw new Exception("Exception $message");
}
}
#Destroy Instance
To destroy an instance, we first stop it.
public function destroy_instance($service_name, $instance)
{
try {
if (!$this->stop_livekit($service_name, $instance, config('ovh.OVH_LB_USER'))) {
throw new Exception('cannot stop livekit');
}
$this->ovh->post("/cloud/project/$service_name/instance/$instance_id/stop");
WaitInstance::dispatch($instance_id, 'stopped');
return true;
} catch (GuzzleHttp\Exception\ClientException $e) {
$response = $e->getResponse();
$responseBodyAsString = $response->getBody()->getContents();
// notify on failure to destroy
throw new Exception("ClientExcepion cannot do OVH action destroy_instance on instance $instance_id $responseBodyAsString");
} catch(GuzzleHttp\Exception\ServerException $e) {
$response = $e->getResponse();
$responseBodyAsString = $response->getBody()->getContents();
// notify on failure to destroy
throw new Exception("ServerExcepion cannot do OVH action destroy_instance on instance $instance_id $responseBodyAsString");
} catch(Exception $e) {
// notify on failure to destroy
throw new Exception("cannot do destroy instance $instance_id " . $e->getMessage());
}
}
We launch a job to monitor the stopping of the instance:
WaitInstance::dispatch($instance_id, 'stopped');
#Monitoring Progress
class WaitInstance implements ShouldQueue, ShouldBeEncrypted
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $ovh;
/**
* Create a new job instance.
*/
public function __construct(private string $instance_id, private string $goal, private int $times = 0)
{
$this->onQueue('ovh');
}
}
Where $goal
is started
or stopped
.
public function handle(): void
{
$this->ovh = new OVH();
$instance = $this->ovh->get_instance(config('ovh.OVH_SERVICE_NAME'), $this->instance_id);
}
We get the instance and check its status relative to our goal:
if ($this->goal == 'started' && $instance['status'] == 'ACTIVE') {
// instance running
} else if ($this->goal == 'stopped' && $instance['status'] == 'SHUTOFF') {
// instance stopped
// delete instance
$this->ovh->delete("/cloud/project/$service_name/instance/$instance_id");
}
If none of these cases, we enter into a loop of waiting a maximum of $times, each time for 1
minute. So we dispatch again the WaitInstance
job with a delay of 1
minute.
if ($this->times + 1 < config('ovh.number_of_waits')) { // still can loop
// dispatch the wait job in a delay of 1 minute
WaitInstance::dispatch($this->instance_id, $this->goal, $this->times + 1)->delay(now()->addMinutes(1));
}
Yoram Kornatzky