Полное покрытие API
Клиент дописан до всех двадцати семи эндпоинтов платформы. Добавлено: - регионы, образы с разбивкой на системы и готовые сборки, вторая версия API образов - все действия над сервером: старт, стоп, перезагрузка, переустановка, смена тарифа, клонирование, откат, консоль, бэкапы - снимки целиком, включая ожидание готовности и поиск поднятых из них серверов - ключи доступа, IP-адреса с обратными записями, приватные сети, kubeconfig - история операций, биллинг, расход в час и в сутки - сборка и проверка slug тарифа по правилам платформы - ошибки разложены по смыслу, чтобы ловить прицельно Claude-Session: https://claude.ai/code/session_01Loz5stgQZH6ofBdMqTxiJu
Этот коммит содержится в:
+114
-63
@@ -2,8 +2,9 @@
|
||||
|
||||
Клиент к API облака REG.RU (Рег.облако), точка входа `https://api.cloudvps.reg.ru`.
|
||||
|
||||
Создание и удаление серверов, снимки, SSH-ключи, тарифы, образы, баланс и цены.
|
||||
Само API устроено как у DigitalOcean, сервер там называется reglet.
|
||||
Полное покрытие API: серверы и все действия над ними, снимки, образы, тарифы, регионы,
|
||||
SSH-ключи, IP-адреса и обратные записи, приватные сети, kubeconfig кластеров,
|
||||
история операций и биллинг.
|
||||
|
||||
## Установка
|
||||
|
||||
@@ -12,104 +13,154 @@ pip install git+https://git.stackworks.ru/viktor/regcloud-api.git
|
||||
```
|
||||
|
||||
Токен берётся в личном кабинете, в разделе настроек, и кладётся в `REGRU_CLOUD_TOKEN`.
|
||||
Либо передаётся в конструктор явно.
|
||||
|
||||
## Быстрый старт
|
||||
## Осмотреться
|
||||
|
||||
```python
|
||||
from regcloud_api import RegCloud
|
||||
|
||||
api = RegCloud()
|
||||
|
||||
for s in api.list():
|
||||
print(s["id"], s["name"], s["status"], RegCloud.ip_of(s))
|
||||
for r in api.regions(): # где можно размещаться
|
||||
print(r["slug"], r["name"])
|
||||
|
||||
print(api.balance())
|
||||
for i in api.distributions(): # операционные системы
|
||||
print(i["slug"], i["distribution"])
|
||||
|
||||
for i in api.applications(): # готовые сборки: панели, докер, гитлаб
|
||||
print(i["slug"])
|
||||
|
||||
for s in api.sizes(): # тарифы
|
||||
print(s["slug"], s["vcpus"], "ядер", s["price"], "р/час")
|
||||
```
|
||||
|
||||
Поднять сервер и дождаться готовности.
|
||||
Во второй версии API образов заметно больше и они разложены по регионам.
|
||||
|
||||
```python
|
||||
srv = api.create(
|
||||
size="c2-m4-d60-hp", # 2 ядра, 4 ГБ, 60 ГБ, линейка «производительный»
|
||||
for i in api.images_v2("openstack-spb1", type="distribution"):
|
||||
print(i["id"], i["name"])
|
||||
```
|
||||
|
||||
## Тарифы
|
||||
|
||||
Slug собирается по шаблону `c<ядра>-m<память>-d<диск>-<линейка>`. Клиент умеет
|
||||
собирать его сам и заодно проверять, что такое сочетание вообще существует.
|
||||
|
||||
```python
|
||||
from regcloud_api import build_size, parse_size
|
||||
|
||||
RegCloud.make_size(4, 8, 120, "hp") # 'c4-m8-d120-hp'
|
||||
build_size(4, 5, 120, "hp") # ValueError, при 4 ядрах памяти 5 ГБ не бывает
|
||||
|
||||
parse_size("c8-m32-d128-g1-a4000-gpu")
|
||||
# {'gpu': 'A4000 16 ГБ', 'vcpus': 8, 'memory_gb': 32, 'disk_gb': 128, 'price_hour': 37.8}
|
||||
```
|
||||
|
||||
Линейки: `std` стандартный, `stdp` стандартный плюс, `hp` производительный,
|
||||
`hps` производительный ФЗ-152, `ddc` выделенный CPU, `high` высокочастотный.
|
||||
|
||||
## Жизненный цикл сервера
|
||||
|
||||
```python
|
||||
key = api.add_ssh_key("deploy", "~/.ssh/id_ed25519.pub") # можно путём к файлу
|
||||
|
||||
srv = api.create_server(
|
||||
size="c2-m4-d60-hp",
|
||||
image="ubuntu-24-04-amd64",
|
||||
name="web-01",
|
||||
name=api.random_name(), # платформа умеет придумывать имена
|
||||
region="openstack-msk1",
|
||||
ssh_keys=["d7:3a:b4:..."], # отпечаток ключа из api.ssh_keys()
|
||||
ssh_keys=[key["fingerprint"]],
|
||||
)
|
||||
|
||||
info = api.wait_active(srv["id"])
|
||||
print("адрес:", RegCloud.ip_of(info))
|
||||
```
|
||||
|
||||
Снять образ и поднять из него копию.
|
||||
Действия над сервером.
|
||||
|
||||
```python
|
||||
api.snapshot(srv["id"], "web-01-base", offline=True) # сервер остановит сам
|
||||
copy = api.create(size="c2-m4-d60-hp", image=7887485, region="openstack-msk1")
|
||||
api.stop(id); api.start(id); api.reboot(id)
|
||||
api.resize(id, "c4-m8-d120-hp") # сменить тариф
|
||||
api.rebuild(id, "debian-12-amd64") # переустановить систему
|
||||
api.reset_password(id)
|
||||
api.vnc_link(id) # консоль, когда по ssh уже не зайти
|
||||
api.set_backups(id, True)
|
||||
api.clone(id, "копия")
|
||||
```
|
||||
|
||||
Удалить.
|
||||
Снимки.
|
||||
|
||||
```python
|
||||
api.destroy(srv["id"])
|
||||
api.snapshot(id, "base", offline=True) # платформа сама остановит сервер
|
||||
snap = api.wait_snapshot("base")
|
||||
api.create_server(size="c2-m4-d60-hp", image=snap["id"], region="openstack-msk1")
|
||||
api.servers_from_snapshot(snap["id"]) # кто уже поднят из этого снимка
|
||||
api.delete_snapshot(snap["id"])
|
||||
```
|
||||
|
||||
## Методы
|
||||
Удаление.
|
||||
|
||||
| Метод | Что делает |
|
||||
|---|---|
|
||||
| `list()`, `get(id)` | серверы на аккаунте |
|
||||
| `create(size, image, name, ssh_keys, region)` | создать сервер, из образа или из снимка |
|
||||
| `destroy(id)` | удалить |
|
||||
| `stop(id)`, `start(id)` | выключить, включить |
|
||||
| `action(id, type, **kw)` | любое действие: reboot, rebuild, resize, clone, restore и прочие |
|
||||
| `snapshot(id, name, offline)` | снять образ |
|
||||
| `wait_active(id)`, `wait_action(id)` | дождаться готовности сервера или завершения действия |
|
||||
| `sizes()`, `gpu_sizes()` | тарифы |
|
||||
| `images()`, `snapshots()` | образы и снимки |
|
||||
| `ssh_keys()` | ключи доступа |
|
||||
| `balance()`, `prices()` | деньги |
|
||||
| `RegCloud.ip_of(server)` | вытащить адрес из карточки сервера |
|
||||
```python
|
||||
api.destroy_server(id) # дождётся снятия блокировки
|
||||
```
|
||||
|
||||
## Особенности API, которые тут уже обойдены
|
||||
## Адреса и сети
|
||||
|
||||
Это не претензии к платформе, просто места, где поведение отличается от ожидаемого.
|
||||
Собрано на живых серверах, чтобы вы на них не наступали.
|
||||
```python
|
||||
api.ips(server_id)
|
||||
api.add_ip(server_id, ipv4=1)
|
||||
api.set_ptr("1.2.3.4", "mail.example.com")
|
||||
api.delete_ip("1.2.3.4")
|
||||
|
||||
**Адрес сервера лежит в поле `ip`.** Поле `networks`, которое по документации должно
|
||||
его содержать, на практике всегда пустое. `ip_of()` смотрит в оба места.
|
||||
vpc = api.create_vpc("backoffice", region="openstack-msk1")
|
||||
api.vpc_attach(vpc["id"], server_id)
|
||||
api.vpc_members(vpc["id"])
|
||||
api.vpc_detach(vpc["id"], server_id)
|
||||
```
|
||||
|
||||
**Снимок снимается только с работающего сервера.** У остановленного API отвечает
|
||||
`SERVER_IS_STOPPED`. Останавливать его для снимка должна сама платформа,
|
||||
для этого есть параметр `offline`.
|
||||
## Деньги
|
||||
|
||||
**Сразу после снимка сервер сидит в `locked`** и на удаление отвечает
|
||||
`RESOURCE_DELETE_ERROR`. Отпускает примерно через полминуты. Поэтому `destroy()`
|
||||
по умолчанию повторяет попытки, пока не получится, и считает `NO_SUCH_REGLET`
|
||||
успехом. Забытый сервер продолжает тарифицироваться, так что молча сдаваться тут нельзя.
|
||||
```python
|
||||
api.money_left() # сколько на счету
|
||||
api.burn_rate() # сколько капает в час, в сутки и в месяц
|
||||
api.billing_history()
|
||||
api.history(limit=50) # что и когда делали с ресурсами
|
||||
api.prices()
|
||||
```
|
||||
|
||||
**`sizes()` показывает не все тарифы.** Часть линеек метод не возвращает, хотя создание
|
||||
с ними работает нормально. Полный список смотрите в панели управления, в разделе
|
||||
настроек, пункт со slug'ами для API и Terraform. Наша выборка лежит в [docs_slugs.md](docs_slugs.md).
|
||||
## Ошибки
|
||||
|
||||
Все ошибки платформы разложены по смыслу, чтобы ловить прицельно.
|
||||
|
||||
```python
|
||||
from regcloud_api import NotFound, Locked, ValidationError, InsufficientFunds
|
||||
|
||||
try:
|
||||
api.destroy_server(123)
|
||||
except NotFound:
|
||||
pass # его уже нет, и хорошо
|
||||
except Locked:
|
||||
pass # занят другой операцией
|
||||
```
|
||||
|
||||
## Особенности платформы, обойдённые внутри
|
||||
|
||||
Собрано на живых серверах, чтобы вы на это не наступали.
|
||||
|
||||
**Адрес сервера лежит в поле `ip`.** Поле `networks` из документации всегда пустое.
|
||||
`RegCloud.ip_of()` смотрит в оба места.
|
||||
|
||||
**Снимок снимается только с работающего сервера.** У остановленного платформа отвечает
|
||||
`SERVER_IS_STOPPED`, останавливать его должна она сама по параметру `offline`.
|
||||
|
||||
**После снимка сервер сидит в `locked`** и на удаление отвечает `RESOURCE_DELETE_ERROR`.
|
||||
Отпускает примерно через полминуты, поэтому `destroy_server()` повторяет попытки.
|
||||
Забытый сервер продолжает тарифицироваться, молча сдаваться тут нельзя.
|
||||
|
||||
**`sizes()` возвращает не все линейки.** Тарифы с видеокартами в выдачу не попадают,
|
||||
хотя создание с ними работает. Их список лежит в `gpu_sizes()` и в [docs_slugs.md](docs_slugs.md).
|
||||
|
||||
**Тарификация идёт от часа.** Двадцать минут работы списываются как полный час.
|
||||
Короткие запуски невыгодны, задачи лучше копить и выполнять пачкой.
|
||||
|
||||
## Составление slug тарифа
|
||||
|
||||
`c<ядра>-m<память>-d<диск>-<линейка>`, например `c4-m8-d120-hp`.
|
||||
|
||||
Линейки: `std` стандартный, `stdp` стандартный плюс, `hp` производительный,
|
||||
`hps` производительный ФЗ-152, `ddc` выделенный CPU, `high` высокочастотный.
|
||||
Допустимые сочетания ядер, памяти и диска смотрите в [docs_slugs.md](docs_slugs.md).
|
||||
|
||||
## Регионы
|
||||
|
||||
`openstack-msk1`, `openstack-msk2`, `openstack-msk3`, `openstack-spb1`,
|
||||
`openstack-sam1`, `openstack-fz1` и архивный `msk1`.
|
||||
|
||||
Не все образы и тарифы доступны в каждом регионе.
|
||||
|
||||
## Рядом
|
||||
|
||||
|
||||
Ссылка в новой задаче
Block a user