
У вас парк VPS. Пять, пятьдесят — неважно. В какой-то момент каждый инженер упирается в одно и то же: где кончилось место, кто загрузил CPU на максимум, а кто простаивает?
Ставить на каждый сервер тяжелый мониторинг ради пары метрик — удовольствие сомнительное. Особенно поймут DevOps-инженеры, отвечающие за десяток проектов, и независимые разработчикм с сетью edge-нод…
Поэтому мы покажем, как быстро собрать свой инструмент: легкий CLI на Go, который достает ключевые показатели прямо из API is*hosting. Минимум лишнего — только то, что нужно видеть в 3 ночи, когда сервер «вдруг» решил подвиснуть.
Что потребуется:
- Базовые знания Go (без лишних сложностей).
- Аккаунт в is*hosting.
- Хотя бы один активный VPS.
Подготовка VPS
Если у вас уже есть VPS от is*hosting — этот шаг можно пропустить и сразу переходить к созданию API-ключа. Если нет, закажите новый: выберите подходящий тариф VPS и пройдите процесс заказа в панели управления.
После этого откройте Client Area и перейдите в раздел API в меню слева. Нажмите кнопку Add API key, придумайте название для ключа (например, default для первого ключа) и сохраните его.
Для целей этого проекта достаточно сохранить ключ в переменной окружения. В реальных условиях лучше использовать менеджер секретов.
На своей машине задайте переменную окружения с именем ISHOSTING_API_KEY. Именно к ней будет обращаться код:
Теперь ваш VPS готов к мониторингу. Следующий шаг — настройка проекта на Go.
Настройка проекта на Go
Создайте новый каталог для проекта, перейдите в него и инициализируйте модуль Go:
go mod init <module-path>
где <module-path> может быть чем-то вроде github.com/yourusername/vps-monitor, если вы планируете поделиться им позже, или просто vps-monitor если он останется на вашем компьютере.
⚠️ Используйте Go 1.21 или новее — именно в этой версии добавлена встроенная функция min(), которая применяется в примерах.
Теперь создайте пустой файл main.go, который мы будем постепенно наполнять кодом. Первым шагом добавим импорты и константу:
package main
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"text/tabwriter"
"time"
)
const (
baseURL = "https://api.ishosting.com"
)
Далее определим несколько типов, начиная с VPSList. Этот тип представляет ответ API на запрос к эндпоинту /vps/list. (Подробнее о нем чуть позже.)
Чтобы не писать типы вручную, можно воспользоваться сокращенным путем: получить JSON-ответ из документации API по эндпоинту /vps/list и прогнать его через инструмент вроде JSON-to-Go, который автоматически создаст описание структур.
Если нужно сгенерировать код для большего числа эндпоинтов is*hosting API, можно использовать его полную спецификацию совместно с инструментами openapi-codegen или ogen.
Ниже приведен тип VPSList — это срез структур с вложенными структурами:
type VPSList []struct {
ID string `json:"id"`
Name string `json:"name"`
Tags []string `json:"tags"`
Location Location `json:"location"`
Plan Plan `json:"plan"`
Platform Platform `json:"platform"`
Status Status `json:"status"`
CreatedAt int `json:"created_at"`
UpdatedAt int `json:"updated_at"`
}
type Location struct {
Name string `json:"name"`
Code string `json:"code"`
}
type Plan struct {
Name string `json:"name"`
Code string `json:"code"`
}
type Platform struct {
Name string `json:"name"`
Code string `json:"code"`
}
type Status struct {
Name string `json:"name"`
Code string `json:"code"`
Message string `json:"message"`
Uptime int `json:"uptime"`
CPU CPU `json:"cpu"`
RAM RAM `json:"ram"`
Drive Drive `json:"drive"`
Network Network `json:"network"`
State State `json:"state"`
}
type CPU struct {
Value string `json:"value"`
Message any `json:"message"`
}
type RAM struct {
Value string `json:"value"`
Message any `json:"message"`
}
type Drive struct {
Value string `json:"value"`
Message string `json:"message"`
}
type Network struct {
Value string `json:"value"`
Message any `json:"message"`
}
type State struct {
Name string `json:"name"`
Code string `json:"code"`
Message any `json:"message"`
}
Для работы с ошибками определим новый тип HTTPStatusError. Он позволяет захватывать HTTP-статус (код и строку) вместе с телом ответа, которое может содержать дополнительную информацию об ошибке.
type HTTPStatusError struct {
Code int // e.g., 200
Status string // e.g., "200 OK"
Body []byte
}
func (e HTTPStatusError) Error() string {
return fmt.Sprintf("HTTP %s", e.Status)
}
А вот отсюда сожно приступить к реализации API-клиента для конечной точки /vps/list.
Отправка аутентифицированных GET-запросов
Определим минималистичный клиент для работы с API is*hosting. Он хранит базовый URL, API-ключ и HTTP-клиент с таймаутом 30 секунд. Затем добавим метод Get() для отправки запросов с заголовком X-Api-Token, а также Accept и нестандартным User-Agent.
type IshostingClient struct {
baseURL string
apiKey string
client *http.Client
}
func NewIshostingClient(baseURL, key string) *IshostingClient {
return &IshostingClient{
baseURL: baseURL,
apiKey: key,
client: &http.Client{
Timeout: 30 * time.Second,
},
}
}
IshostingClient управляет базовым URL (на случай, если он изменится), API-ключом и встроенным HTTP-клиентом с таймаутом 30 секунд, чтобы запросы не зависали бесконечно.
Метод Get() принимает полный URL (база + эндпоинт + query-параметры), выставляет заголовок X-Api-Token для аутентификации, а также Accept и User-Agent.
Нюанс: некоторые системы безопасности помечают дефолтный User-Agent Go как «подозрительный», поэтому задайте уникальный User-Agent.
Метод отправляет запрос, читает тело ответа и возвращает его. Если код статуса указывает на ошибку, возвращается HTTPStatusError с кодом, строкой статуса и телом ответа.
func (c *IshostingClient) Get(query string) (body []byte, err error) {
req, err := http.NewRequest("GET", query, nil)
if err != nil {
return nil, fmt.Errorf("cannot create request: %w", err)
}
// set request headers for authentication and accepted format
req.Header.Add("X-Api-Token", c.apiKey)
req.Header.Add("Accept", "application/json")
req.Header.Add("User-Agent", "go-ishosting-api-client/1.0")
// send the request
resp, err := c.client.Do(req)
if err != nil {
return nil, fmt.Errorf("cannot send request: %w", err)
}
defer resp.Body.Close()
// read the response
body, err = io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("reading response failed: %w", err)
}
// check for HTTP errors
if resp.StatusCode >= 400 {
return nil, HTTPStatusError{
Code: resp.StatusCode,
Status: resp.Status,
Body: body,
}
}
return body, nil
}
Этот метод уже позволяет отправлять запросы к эндпоинтам is*hosting, но использовать его напрямую не планируется — дальше мы напишем специальный метод для вызова /vps/list.
Получение списка VPS и статусов
Эндпоинт /vps/list возвращает список всех ваших VPS вместе с полезной мониторинговой информацией — текущей загрузкой CPU, RAM, диска и т. п. Одного API-запроса достаточно, чтобы получить срез состояния по всем серверам.
Метод VpsList() собирает эти данные и возвращает их в виде VPSList. Для производительности API разбивает большие выборки на «страницы». Параметром limit задается количество VPS на страницу; чтобы получить следующую страницу, увеличивайте offset на 1.
Чтобы избежать ошибок при ручной сборке URL-строк, используем пакет net/url и формируем запрос типобезопасно:
func (c *IshostingClient) VpsList(limit, offset int) (list VPSList, err error) {
// construct the query URL, starting with the base URL
query, err := url.Parse(c.baseURL)
if err != nil {
return nil, fmt.Errorf("url.Parse: %w", err)
}
// add the endpoint
query = query.JoinPath("/vps/list")
// add query parameters
params := url.Values{}
params.Add("limit", fmt.Sprint(limit))
if offset > 0 {
params.Add("offset", fmt.Sprint(offset))
}
query.RawQuery = params.Encode()
// pass the query URL to Get()
body, err := c.Get(query.String())
if err != nil {
return nil, fmt.Errorf("GET /vps/list: %w", err)
}
// turn the returned JSON information into a VPSList slice
err = json.Unmarshal(body, &list)
if err != nil {
return nil, fmt.Errorf("unmarshal VPS list: %w", err)
}
return list, nil
}
С методами Get() и VpsList() у нас есть все, чтобы получать мониторинговые данные; дальше — про вывод результатов в терминал.
Отображение данных в терминале
Эндпоинт /vps/list возвращает JSON-объект, но для мониторинга удобнее видеть результаты прямо в терминале. Для этого напишем функцию PrintVPSStats(), которая с помощью пакета text/tabwriter выведет таблицу со всеми ключевыми параметрами:
func PrintVPSStats(vpsList VPSList) {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
defer w.Flush()
// Print header
fmt.Fprintln(w, "ID\tName\tLocation\tStatus Name\tStatus Message\tCPU\tRAM\tDrive\tNetwork\tState")
fmt.Fprintln(w, "---\t---\t---\t---\t---\t---\t---\t---\t---\t---")
// Print each VPS information
for _, vps := range vpsList {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
vps.ID,
vps.Name,
vps.Location.Name,
vps.Status.Name,
vps.Status.Message,
vps.Status.CPU.Value,
vps.Status.RAM.Value,
vps.Status.Drive.Value,
vps.Status.Network.Value,
vps.Status.State.Name,
)
}
}
Финальное связывание и первый тестовый запуск
Осталось всего ничего:
- считать API-ключ из переменной окружения;
- создать IshostingClient;
- вызвать VpsList();
- вывести результаты таблицей.
Все это делается в main():
func main() {
key := os.Getenv("ISHOSTING_API_KEY")
if len(key) == 0 {
log.Fatal("Please set ISHOSTING_API_KEY")
}
c := NewIshostingClient(baseURL, key)
list, err := c.VpsList(10, 0)
if err != nil {
var httpErr HTTPStatusError
if errors.As(err, &httpErr) {
log.Fatalf("HTTP %s with body %s", httpErr.Status, string(httpErr.Body)[:min(len(httpErr.Body), 100)])
}
log.Fatalf("cannot list VPSs: %v", err)
}
PrintVPSStats(list)
}
Код готов к запуску. Можно либо последовательно вставить его в ваш main.go, либо взять целиком из песочницы Go (учтите, что без переменной окружения пример там не запустится, и не вставляйте API-ключ в публичные сервисы).
Долгожданный запуск:
go run .
Если у вас, например, один VPS в Германии и другой VPS в Нидерландах, вывод будет похож на:
ID Name Location Status Name Status Message CPU RAM Drive Network State
--- --- --- --- --- --- --- --- --- ---
DE_12356212_3 HSE Group Servers Germany Running Uptime: 0 Days, 18:01:34 0.1% 52% 19 Gb ~34,5 Mb/s Active
NL_12356212_3 HSE Group Servers Nederland Running Uptime: 0 Days, 18:01:34 2.4% 34% 12 Gb ~5,1 Mb/s Active
Выводы и следующие шаги
Несколько десятков строк Go — и у вас уже свой мониторинг VPS. Работает быстро, показывает честные цифры, без красивых дашбордов и лишней нагрузки. Хотите больше? Добавьте:
- Постраничную выборку — цикл для получения информации о более чем 10 VPS (батчами по 10).
- Цветной вывод — подсветка статусов и порогов загрузки в терминале.
- Автообновление — периодический опрос (каждые n минут) с обновлением экрана.
Это база. Дальше — ваш выбор: расширять под продакшен или оставить минимализм. Главное, что теперь вы сами управляете мониторингом, а не наоборот.