Search
Write a publication
Pull to refresh

Vultr как альтернатива Digital Ocean и работа с его API на PHP

Все знают, что если вам нужны VPS с Linux с почасовой оплатой, то вам прямая дорога в Digital Ocean (далее DO). О нём написано уже много, и знает о нём почти каждый. Но мало кто знает, что у него есть хорошая альтернатива, которая, на мой взгляд, абсолютно незаслуженно была обделена вниманием. На момент написания этой статьи на Хабре всего в трёх статьях упоминается Vultr, и более-менее подробного его обзора нет ни в одной из них.

И, как мне кажется, зря. IMHO, Vultr — это «убийца» Digital Ocean, и дальше я объясню почему.

Vultr во многом похож на DO. Он буквально кричит: «У меня всё то же самое! Нравится DO? Попробуй меня!». Действительно, сами по себе услуги, тарифы, возможности и сервисы — чуть ли не полностью копировали DO. Я не очень люблю плагиаторов, но при детальном рассмотрении я пришёл к выводу, то Vultr практически во всём превосходит DO.

Посудите сами, вот неполный список преимуществ Vultr:

  • В минимальном тарифе ($5/мес) включено 768 Mb RAM против 512 Mb у DO
  • 14 дата центров против 7 у DO (включая такое редкое расположение как Япония и Австралия)
  • Наличие тарифных планов под дешевое «хранилище» (SATA вместо SSD)
  • Есть возможность арендовать не VPS, а Dedicated Server
  • Есть возможность установить любой дистрибутив со своего ISO-образа
  • Есть возможность установить Windows Server (+$16/мес к стоимости любого тарифа)
  • Наличие Startup Scripts (подробнее ниже)
  • Полностью бесплатное хранение снэпшотов (даже их создание — бесплатно)
  • Намного проще регистрация (не требуется идентификация нового пользователя с помощью фото с паспортом)

На некоторых пунктах хочется остановиться поподробнее. Vultr имеет 3 линейки тарифных планов: SSD, Storage и Dedicated (из них у DO есть только первая). Т.е. если Ваш проект подразумевает хранение большого объёма данных (например, более 100 Гб), то в случае с DO вам придётся взять тариф за $160/мес. В то время как у Vultr вы можете взять Storage-тариф, в котором вместо SSD установлен SATA-массив и тариф, в который включено 125 Gb жёсткого диска обойдётся Вам всего в $5/мес. Да, в этом тарифе всего 512 Mb RAM, но можно посмотреть тарифы подороже. Скажем, 1 Gb RAM будет стоить $10/мес, только там ещё и жёсткий диск увеличат до 250 Gb. А такой объём данных в DO будет стоить уже $320/мес. В общем, тарифные планы в Vultr значительно более гибкие. Вы могли бы предположить, что за такой щедростью скрывается более дешевая система виртуализации, но нет. Vultr использует для всех своих машин виртуализацию KVM. Как и DO.

В купе с предыдущим плюсом отмечу важный их плюс — Vultr не берёт деньги ни за создание снэпшотов (в отличие от DO), ни за их хранение, ни за разворачивание. Совсем! И так уже несколько лет и менять пока не планируют (хотя пользовательское соглашение это допускает). Получается, что вы за $5/мес (или 0.7 цента в час) можете создать себе VPS с диском 125 Гб, скриптом ещё на этапе создания (об этом позже) поднять там FTP, подключиться со своей локальной машины туда, передать весь своих архив фото/музыки (у кого не влезет — есть тарифы до 1 Тб за $40/мес или 6 центов в час), после этого снять снэпшот с этой машины и удалить машину. Всё. У вас бесплатно хранится до терабайта вашей информации «на чёрный день». Если она Вам понадобится — заново создаете сервер из этого снэпшота, подключаетесь по FTP, забираете что нужно и удаляете машину. А для удобства, вместо одного огромного сервера можете создать несколько поменьше (по тематике хранения информации), тогда снэпшоты будут быстрее сохраняться/разворачиваться, да и за работу с ними будут брать меньше денег.

Ещё отмечу важную killer-фичу Vultr в сравнении с DO — это «Startup Scripts». Вы можете в своём личном кабинете создать несколько готовых скриптов, вбивая туда команды bash. И этот код исполнится с root-правами сразу после создания виртуальной машины. Внимательный читатель может сейчас воскликнуть: «Ба, так у Digital Ocean вообще-то тоже есть подобная фича и называет User Data или Cloud Script», но будет не совсем прав. Во-первых, у DO это не совсем команды bash. Скрипт нужно писать на YAML. Да, в него можно упаковать нативные команды bash, но перед этим вам придётся почитать мануалы как всё это оформить правильно, чтобы скрипт сработал. Во-вторых (и это более важно, имхо), DO не позволяет хранить эти скрипты в личном кабинете. Т.е. если Вы постоянно разворачиваете какие-то сервера и хотите постоянно применять к ним типовые скрипты, то Вам придётся где-то отдельно хранить эти скрипты в блокнотике и каждый раз «копипастить» в окно создания дроплета. Не очень здорово для облачного хостинга хранить свои скрипты где-то отдельно. А если учесть, что в типовые дистрибутивы CentOS на DO не входит даже wget, то лично я скриптами пользуюсь при каждом создании сервера.

В случае же с Vultr, я заранее создал несколько типовых скриптов и при создании VPS просто выбираю нужный вот так:



И в данном примере после создания сервера у меня сразу развернётся VPN-сервер и я сразу могу к нему подключиться вообще не открывая SSH-терминал.

Но этого мне показалось мало и я понял, что хочу работать с Vultr по API, не заходя постоянно в личный кабинет. Я перерыл весь интернет и нигде не нашел каких-то готовых инструментов. Что в этом случае делают Хабраюзеры? Правильно, берут и пишут сами. Писать решил на PHP. Спасибо Vultr за хорошие мануалы (опять же, на мой взгляд, намного удобнее, чем описание API у DO), за пару дней накидал полноценный PHP Wrapper для API Vultr.

Что он из себя представляет: при первичном открытии страницы я запрашиваю ключ API:



И тут же перезагружаю ту же самую страницу, но уже с помощью GET-запроса передаю в неё ключ API. Соответственно, при открытии страницы просто стоит проверка был ли GET-запрос. Если не было — запрос на ввод ключа. Если был — открывается тело страницы. В теле страницы происходит несколько GET-запросов на API VULTR получаются данные и структурированные отображаются в таблице. Как-то так:



Большую часть занимает форма создания нового сервера. По API также подтягиваются все ваши снэпшоты и Startup Scripts.

Обратите внимание, что здесь же показываются данные биллинга (остаток на счёте). К примеру, API v2 Digital Ocean вообще не поддерживает работу с биллингом и для проверки баланса вам всё равно придётся заходить в личный кабинет. В случае с Vultr в этом нет необходимости.

Да, в примере выше у меня нет созданных серверов. Когда они есть, то их отображение происходит так:



Соответственно, все сервера сведены в одну таблицу. Тут же есть кнопка «удалить». Если сервер в соответствующем статусе, то есть кнопка «терминал», которая в браузере в новом окне открывает терминальный доступ к этому серверу по SSH.

Предоставляю полный листинг исходников:

vultr.php (основой скрипт)
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<TITLE>Vultr API</TITLE>
  <style>
   table, tr{
    border: 0px solid black;   
    border-collapse: collapse;
   } 
   tr {
   valign: center;
   }
   td { 
    border: 1px solid black;
    padding-top: 1px;
    padding-bottom: 2px;
    padding-right: 5px;
    padding-left: 5px;
    border-collapse: collapse;
   }
   .hide {
   border-left: 0px;
   border-right: 0px;
   border-top: 0px;
   border-bottom: 0px;
   padding: 0px;
   padding-left: 5px;
   }
   a {
   text-decoration: none;
   }
  </style>
</HEAD>
<BODY>
<?php
// Автор: Brizovsky

//Функция преобразования Class Object в массив
 function objectToArray($d) {
 if (is_object($d)) {
 // Gets the properties of the given object
 // with get_object_vars function
 $d = get_object_vars($d);
 }
 
 if (is_array($d)) {
 /*
 * Return array converted to object
 * Using __FUNCTION__ (Magic constant)
 * for recursive call
 */
 return array_map(__FUNCTION__, $d);
 }
 else {
 // Return array
 return $d;
 }
 }

if (!empty($_GET)){ //Идёт проверка введен ли ключ API
 
 //Список используемых запросов к API
$url_regions = "https://api.vultr.com/v1/regions/list";
$url_plans = "https://api.vultr.com/v1/plans/list";
$url_oslist = "https://api.vultr.com/v1/os/list";
$url_scripts = "https://api.vultr.com/v1/startupscript/list";
$url_snapshots = "https://api.vultr.com/v1/snapshot/list";
$url_balance = "https://api.vultr.com/v1/account/info";
$url_servers = "https://api.vultr.com/v1/server/list";
$apikey = $_GET['apikey'];
if ($apikey == "myapi") { //alias для ключа API, который можно запомнить
$apikey = "сюда_вставляете_ваш_ключ_api"; //наш ключ API, который нам лень запоминать
} // эти строчки можно повторить для нескольких алиасов

if (file_get_contents("$url_balance?api_key=$apikey", true) != false){ //Проверка правильный ли ключ API

//Делаем запросы к API, записывем ответы
$regions_raw = file_get_contents("$url_regions", true);
$regions = objectToArray(json_decode($regions_raw)); //преобразовали сначала в class Object, потом в обычный массив
$plans_raw = file_get_contents("$url_plans", true);
$plans = objectToArray(json_decode($plans_raw)); //преобразовали сначала в class Object, потом в обычный массив
$oslist_raw = file_get_contents("$url_oslist", true);
$oslist = objectToArray(json_decode($oslist_raw)); //преобразовали сначала в class Object, потом в обычный массив
$scripts_raw = file_get_contents("$url_scripts?api_key=$apikey", true);
$scripts = objectToArray(json_decode($scripts_raw)); //преобразовали сначала в class Object, потом в обычный массив
$snapshots_raw = file_get_contents("$url_snapshots?api_key=$apikey", true);
$snapshots = objectToArray(json_decode($snapshots_raw)); //преобразовали сначала в class Object, потом в обычный массив
$balance_raw = file_get_contents("$url_balance?api_key=$apikey", true);
$balance = objectToArray(json_decode($balance_raw)); //преобразовали сначала в class Object, потом в обычный массив
$servers_raw = file_get_contents("$url_servers?api_key=$apikey", true);
$servers = objectToArray(json_decode($servers_raw)); //преобразовали сначала в class Object, потом в обычный массив


//Вывод баланса
$bal = abs($balance[balance]);//берём баланс по модулю, т.к. он почему-то идёт с отрицательным знаком
$charge = abs($balance[pending_charges]); //тоже берем по модулю
$bal = $bal - $charge; //Вычитаем из баланса текущие расходы, т.к. по API он показывает остаток на начала месяца, а не текущий, хотя в личном кабинете показывает именно текущий остаток.
echo "Ваш баланс: $$bal
"; echo "\n";
echo "Расход в этом месяце: $$charge

"; echo "\n";

//вывод серверов
if (!empty($servers)){ //проверяем есть ли сервера
echo "Ваши сервера:
\n";
echo "<table cellspacing=0>\n";
echo "<tr align=center><td>Номер</td><td>Название</td><td>ОС</td><td>Ядер</td><td>RAM</td><td>Диск</td><td>IP</td><td>Город</td><td>Пароль</td><td>Статус</td><td>Дата создания</td><td class=hide colspan=2></td></tr>\n";
foreach($servers as $obj): //перебираем все сервера, если есть
echo "<tr align=center><td>$obj[SUBID]</td><td>$obj[label]</td><td>$obj[os]</td><td>$obj[vcpu_count]</td><td>$obj[ram]</td><td>$obj[disk]</td><td>$obj[main_ip]</td><td>$obj[location]</td><td>$obj[default_password]</td><td>$obj[status], $obj[power_status], $obj[server_state]</td><td>$obj[date_created]</td>";
echo '<td class="hide"><form name="form2" method="post" action="vultr_del.php" style="margin: 0px;" '; ?>onsubmit='return confirm("Вы действительно хотите удалить сервер?")'<?php
echo '><input type="hidden" style="margin: 0px;" name="server_id" value="';
echo $obj[SUBID]; //прячем в форме параметр сервера
echo '">'; echo "\n";
echo '<input type="hidden" style="margin: 0px;" name="apikey" value="';
echo $apikey; //добавляем в форму ключ API для передачи в другие скрипты
echo '">'; echo "\n";
echo '<input type="submit" value="удалить" /></form></td>';
echo "<td class=hide>";
if (!empty($obj[kvm_url])){ //проверяем есть ли ссылка на терминал и выводим её только если есть
echo "<a href=$obj[kvm_url] target=_blank><input type=button value=Терминал></a>";
}
echo "</td>";
echo "</tr>\n";
endforeach;
echo "</table>
\n";
 }else{ //если серверов нет
echo "У вас нет созданных серверов.

\n";
 }


//начало формы на создание сервера
echo "Выберите параметры нового сервера:<br>\n";
echo '<form style="width: 450px; border: 1px groove #000000;" name="form1" method="post" action="vultr_create.php"><div style="margin: 10;">'; echo "\n";
echo '<input type="hidden" style="margin: 0px;" name="apikey" value="';
echo $apikey; //добавляем в форму ключ API для передачи в другие скрипты
echo '">'; echo "\n";
echo "Название сервера:
\n";
echo '<input type="text" name="label" width=15>
<font size=1> </font>
';
echo "\n";

//вывод дата-центров
echo "Дата-центр:<br>\n";
echo "<select id='dc' name='dc'>\n";
foreach($regions as $obj): //перебираем все дата-центры
echo "<option value='$obj[DCID]'";
if ($obj[DCID] == "7") echo " selected"; //проверка: Если это амстердам - выбираем по умолчанию. Если у вас другие предпочтения - меняйте ID (посмотреть можно в исходном html-коде после открытия этой страницы)
echo ">$obj[continent]: $obj[country], $obj[name]</option>\n";
endforeach;
echo "</select>
<font size=1> </font>
\n";

//вывод тарифного плана
echo "Тарифный план:<br>\n";
echo "<select id='plan' name='plan'>\n";
foreach($plans as $obj): //перебираем все планы
$arr = array(); //обнулили временный массив, в котором будем хранить список тарифных планов
echo "<option value='$obj[VPSPLANID]'>$$obj[price_per_month]: $obj[vcpu_count] CPU, $obj[ram] Mb RAM, $obj[disk] Gb $obj[plan_type] (";
foreach($obj[available_locations] as $obj2):
array_push($arr, $regions[$obj2]["country"]); //записали во временный массив список стран
endforeach;
$arr = array_unique($arr); //убрали повторяющиеся значения
foreach($arr as $obj3):
if ($arr[0] != $obj3) echo " "; //проверка: если это не первое значение в массиве, то ставим пробел перед его записью
echo "$obj3";
endforeach;
echo ")</option>\n";
endforeach;
echo "</select>
<font size=1> </font>
\n";

//вывод списка ОС
echo "ОС:<br>\n";
echo "<select id='os' name='os'>\n";
foreach($oslist as $obj):
echo "<option value='$obj[OSID]'>$obj[name]</option>\n";
endforeach;
echo "</select>
<font size=1> </font>
\n";

//вывод списка снапшотов
echo "Снапшот:<br>\n";
echo "<select id='snapshot' name='snapshot'>\n";
echo "<option value='' selected>не использовать</option>\n";
foreach($snapshots as $obj):
echo "<option value='$obj[SNAPSHOTID]'>$obj[description] ($obj[date_created])</option>\n";
endforeach;
echo "</select>
<font size=1> </font>
\n";


//вывод списка Стартовых скриптов
echo "Startup Script:<br>\n";
echo "<select id='script' name='script'>\n";
echo "<option value='' selected>не использовать</option>\n";
foreach($scripts as $obj):
echo "<option value='$obj[SCRIPTID]'>$obj[name]</option>\n";
endforeach;
echo "</select>
<font size=1> </font>
\n";

//конец формы
echo '<input type="submit" value="Создать сервер" />'; echo "\n";
echo "</div></form>\n";
} //Конец места где API ключ есть и он правильный
else { //местл, где API ключ есть, но он неправильный
echo "Ключ API неправильный!

\n";
echo '<a href="vultr.php">Попробовать снова</a>';
}
}
//Ниже идёт первичный вход (не указан ключ API)
else {
echo "Введите ключ API:
";
echo '<form name="form0" method="GET" action="vultr.php">'; echo "\n";
echo '<input type="text" name="apikey">';
echo '<input type="submit" value="Готово" />'; echo "\n";
echo "</form>\n";
}
?>
</BODY>
</HTML>


vultr_create.php (создание сервера)
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<TITLE>Vultr API</TITLE>
<BODY>
<?php
// Функция преобразования class Object в массив
 function objectToArray($d) {
 if (is_object($d)) {
 // Gets the properties of the given object
 // with get_object_vars function
 $d = get_object_vars($d);
 }
 
 if (is_array($d)) {
 /*
 * Return array converted to object
 * Using __FUNCTION__ (Magic constant)
 * for recursive call
 */
 return array_map(__FUNCTION__, $d);
 }
 else {
 // Return array
 return $d;
 }
 }


if (!empty($_POST)){ //проверяем был ли post-запрос или это прямое открывание файла
$url_create = "https://api.vultr.com/v1/server/create";
$dc = $_POST['dc'];
$apikey = $_POST['apikey'];
$plan = $_POST['plan'];
$os   = $_POST['os'];
$snapshot = $_POST['snapshot'];
$script   = $_POST['script'];
$label = $_POST['label'];

if (!empty($snapshot)){ //Если создаем сервер с помощью snapshot, то игнорируем выбранную ОС и отключаем Startup Script
$os = "164";
$postdata = http_build_query(
    array(
        'label' => $label,
        'DCID' => $dc,
        'VPSPLANID' => $plan,
        'OSID' => $os,
        'SNAPSHOTID' => $snapshot
    )
);
}
else { //Это вариант, когда Snapshot не используется
$postdata = http_build_query(
    array(
        'label' => $label,
        'DCID' => $dc,
        'VPSPLANID' => $plan,
        'OSID' => $os,
        'SNAPSHOTID' => $snapshot,
        'SCRIPTID' => $script
    )
);
}
$opts = array('http' => //так мы генерируем правильный контекст для отправки POST-запроса с помощью file_get_contents
    array(
        'method'  => 'POST',
        'header'  => 'Content-type: application/x-www-form-urlencoded',
        'content' => $postdata
    )
);
$uri_create="$url_create?api_key=$apikey"; //генерируем правильную ссылку, с учётом ключа API

$context  = stream_context_create($opts); //генерируем правильный контекст для отправки POST-запроса с помощью file_get_contents

$create_raw = file_get_contents("$uri_create", false, $context); //делаем POST-запрос на API
$create = objectToArray(json_decode($create_raw)); //полученные данные сначла преобразовываем из JSON в Class Object, потом в массив для удобства работы
  if ($create[SUBID] != "") { //Проверяем был ли создан сервер через возрат ID сервера от API
//Сервер был создан
echo "Был создан сервер $create[SUBID]

\n";
echo '<a href="vultr.php?apikey='; //генерируем ссылку "назад" с нашим ключом API, чтобы не запрашивать повторно.
echo $apikey;
echo '">Вернуться назад</a>';
  }
  else // Здесь оказываемся, если сервер не был создан
  {
  echo "Oops. Что-то пошло не так. Сервер не был создан. Возможно, указанный тарифный план не допустим в указанном регионе.

\n";
  echo '<a href="vultr.php?apikey='; //генерируем ссылку "назад" с нашим ключом API, чтобы не запрашивать повторно.
  echo $apikey;
  echo '">Попробовать снова</a>';
  }
}
else { //Сюда попадаем, когда файл открылся без POST-запроса
echo "В скрипт не переданы данные из формы. Прямое открытие файла?

\n";
echo '<a href="vultr.php">Попробовать снова</a>';
}

?>
</BODY>
</HTML>


vultr_del.php (удаление сервера)
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<TITLE>Vultr API</TITLE>
<BODY>
<?php
if (!empty($_POST)){  //проверяем был ли post-запрос или это прямое открывание файла

//собираем данные из POST-запроса
$url_del = "https://api.vultr.com/v1/server/destroy";
$server_id = $_POST['server_id'];
$apikey = $_POST['apikey'];

$postdata = http_build_query(
    array(
        'SUBID' => $server_id
    )
);

$opts = array('http' => //так мы генерируем правильный контекст для отправки POST-запроса с помощью file_get_contents
    array(
        'method'  => 'POST',
        'header'  => 'Content-type: application/x-www-form-urlencoded',
        'content' => $postdata
    )
);
$uri_del="$url_del?api_key=$apikey"; //генерируем правильную ссылку, с учётом ключа API

$context  = stream_context_create($opts); //генерируем правильный контекст для отправки POST-запроса с помощью file_get_contents

$del_raw = file_get_contents("$uri_del", false, $context);  //Делаем POST-запрос к API

echo "Был удален сервер $server_id 

\n";
echo '<a href="vultr.php?apikey='; //генерируем ссылку "назад" с нашим ключом API, чтобы не запрашивать повторно.
echo $apikey;
echo '">Вернуться назад</a>';
}
else { //Сюда попадаем, когда файл открылся без POST-запроса
echo "В скрипт не переданы данные из формы. Прямое открытие файла?

\n";
echo '<a href="vultr.php">Попробовать снова</a>';
}
?>
</BODY>
</HTML>


Все три файла должны лежать в одном месте и называться так, как в заголовке спойлера.

Кстати, когда мне надоело постоянно вводить свой ключ API, поступил следующим образом: сделал легко-запоминаемый алиас. Т.е. я вместо своего ключа API ввожу что-нибудь типа «hello api», а внутри php идёт замена этого значения на мой реальный ключ. Т.е. достаточно запомнить только кодовую фразу. Можете себе так же сделать — править в основном файле в строке 72.

Весь свой код старался хорошо прокомментировать, чтобы несложно было разобраться. Скрипт делал для себя, поэтому особо сложных проверок и «защит от дурака» не ставил, но, тем не менее: сделал подтверждение перед удалением сервера, сделал проверку на правильность ключа API. А, также, предусмотрел наличие взаимоисключающих параметров (к примеру, указание ОС сервера и указание своего Снэпшота).
Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.