examples/VM: sless_job provisioning (packages, nginx, docker)

- Add sless.tf: three sless_job resources with depends_on chain
  - vm-install-packages: jq, python3-pip, htop, unzip
  - vm-install-nginx: nginx 1.18 (HTTP 200 verified)
  - vm-install-docker: Docker CE 29.3.1 + Compose v5.1.1
- Add functions/install-{packages,nginx,docker}/handler.py
  - _wait_apt_lock: cloud-init status --wait + systemctl mask
    fixes Ubuntu 22.04 first-boot apt lock (unattended-upgrades)
  - DPkg::Lock::Timeout=600 on all apt-get calls
- Add variables.tf (install_run_id, vm_public_key, vm_private_key)
- Add outputs.tf (install_*_result)
- Bump nubes provider 5.0.49 -> 5.0.51
- doc/progress.md: document step 6 with bug analysis
This commit is contained in:
Naeel
2026-03-29 19:54:00 +03:00
parent b4c2e7f6b1
commit fef441681e
13 changed files with 1160 additions and 8 deletions
@@ -0,0 +1,158 @@
# 2026-03-29 — handler.py: установка Docker CE на ВМ по SSH.
# sless_job runtime: python3.11, entrypoint: handler.install
#
# Метод установки: официальный Docker apt-репозиторий (best practices).
# НЕ используется curl | sh — небезопасно для продакшена.
#
# event_json:
# compose: true/false — ставить ли docker-compose-plugin (default: true)
#
# env_vars:
# VM_IP: внешний IP ВМ
# SSH_USER: логин (ubuntu)
# SSH_KEY: содержимое приватного SSH-ключа (PEM)
import os, io, time
import paramiko
def _load_key(content):
for cls in (paramiko.Ed25519Key, paramiko.RSAKey, paramiko.ECDSAKey):
try:
return cls.from_private_key(io.StringIO(content))
except Exception:
pass
raise ValueError("Неподдерживаемый тип SSH-ключа")
def _ssh_connect(retries=5, delay=10):
key = _load_key(os.environ["SSH_KEY"])
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
last_err = None
for attempt in range(retries):
try:
client.connect(
hostname=os.environ["VM_IP"],
username=os.environ["SSH_USER"],
pkey=key,
timeout=15,
)
return client
except Exception as e:
last_err = e
if attempt < retries - 1:
time.sleep(delay)
raise RuntimeError(f"SSH не удалось после {retries} попыток: {last_err}")
def _run(client, cmd, timeout=120, check=True):
_, stdout, stderr = client.exec_command(cmd, timeout=timeout)
code = stdout.channel.recv_exit_status()
out = stdout.read().decode(errors="replace").strip()
err = stderr.read().decode(errors="replace").strip()
if check and code != 0:
raise RuntimeError(f"Ошибка (exit {code}):\n{cmd}\nstderr: {err}")
return code, out, err
def _wait_apt_lock(client, attempts=20, delay=10):
"""Ждать завершения cloud-init и убить авто-обновления. Ubuntu 22.04+."""
# Шаг 1: Ждём завершения cloud-init — он держит apt при первом старте VM
_run(client, "timeout 300 sudo cloud-init status --wait 2>/dev/null; true", check=False, timeout=310)
# Шаг 2: Mask (не просто disable) — systemd не сможет перезапустить
_run(client, "sudo systemctl mask unattended-upgrades apt-daily.service apt-daily-upgrade.service apt-daily.timer apt-daily-upgrade.timer 2>/dev/null; true", check=False)
_run(client, "sudo systemctl stop unattended-upgrades apt-daily.service apt-daily-upgrade.service 2>/dev/null; true", check=False)
# Шаг 3: Добить оставшиеся apt/dpkg процессы
_run(client, "sudo pkill -9 -x unattended-upgrades apt-get apt dpkg 2>/dev/null; true", check=False)
_run(client, "sudo kill -9 $(sudo lsof -t /var/lib/dpkg/lock-frontend 2>/dev/null) 2>/dev/null; true", check=False)
# Шаг 4: Убрать стейл-локи и починить dpkg
_run(client, "sudo rm -f /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock /var/lib/apt/lists/lock 2>/dev/null; true", check=False)
_run(client, "sudo dpkg --configure -a 2>/dev/null; true", check=False)
time.sleep(3)
locks = ["/var/lib/dpkg/lock-frontend", "/var/lib/dpkg/lock", "/var/lib/apt/lists/lock"]
for i in range(attempts):
all_free = all(
_run(client, f"sudo flock -n {lock} true 2>/dev/null", check=False)[0] == 0
for lock in locks
)
if all_free:
return
_run(client, "sudo pkill -9 -x apt-get apt dpkg 2>/dev/null; true", check=False)
_run(client, "sudo kill -9 $(sudo lsof -t /var/lib/dpkg/lock-frontend 2>/dev/null) 2>/dev/null; true", check=False)
if i < attempts - 1:
time.sleep(delay)
raise RuntimeError("apt lock занят слишком долго — проверьте процессы на ВМ")
# Команды установки Docker CE через официальный apt-репозиторий.
# Источник: https://docs.docker.com/engine/install/ubuntu/
_DOCKER_INSTALL_CMDS = [
# Зависимости для добавления внешнего репозитория
"sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 install -y -qq ca-certificates curl gnupg",
# Директория для ключей
"sudo install -m 0755 -d /etc/apt/keyrings",
# GPG-ключ Docker
"curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor --batch --yes -o /etc/apt/keyrings/docker.gpg",
"sudo chmod a+r /etc/apt/keyrings/docker.gpg",
# Docker apt-репозиторий
(
'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] '
'https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" '
"| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null"
),
# Обновить индекс с новым репо
"sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 update -qq",
# Установить Docker CE
"sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 install -y -qq docker-ce docker-ce-cli containerd.io",
]
def install(event):
"""Установить Docker CE. Если уже установлен — вернуть версию."""
install_compose = event.get("compose", True)
client = _ssh_connect()
try:
# Проверить: уже установлен?
code, ver_out, _ = _run(client, "docker --version 2>&1", check=False)
if code == 0 and "Docker version" in ver_out:
_, compose_out, _ = _run(client, "docker compose version 2>&1", check=False)
return {
"status": "already_installed",
"docker_version": ver_out,
"compose_version": compose_out if "Docker Compose" in compose_out else None,
}
_wait_apt_lock(client)
for cmd in _DOCKER_INSTALL_CMDS:
_run(client, cmd, timeout=180)
if install_compose:
_run(
client,
"sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq docker-compose-plugin",
timeout=120,
)
# Добавить пользователя в группу docker (чтобы запускать без sudo)
ssh_user = os.environ["SSH_USER"]
_run(client, f"sudo usermod -aG docker {ssh_user}", check=False)
# Проверка: запустить hello-world
# Используем sudo т.к. usermod не применится до переподключения
_run(client, "sudo docker run --rm hello-world", timeout=120)
_, ver_out, _ = _run(client, "docker --version", check=False)
_, compose_out, _ = _run(client, "docker compose version 2>&1", check=False)
return {
"status": "ok",
"docker_version": ver_out,
"compose_version": compose_out if "Docker Compose" in compose_out else None,
"note": f"user '{ssh_user}' added to docker group (reconnect to use without sudo)",
}
finally:
client.close()
@@ -0,0 +1,2 @@
paramiko
# v6
@@ -0,0 +1,129 @@
# 2026-03-29 — handler.py: установка nginx на ВМ по SSH.
# sless_job runtime: python3.11, entrypoint: handler.install
#
# event_json: {} (параметров нет — nginx ставится с дефолтной конфигурацией)
#
# env_vars:
# VM_IP: внешний IP ВМ
# SSH_USER: логин (ubuntu)
# SSH_KEY: содержимое приватного SSH-ключа (PEM)
import os, io, time
import paramiko
def _load_key(content):
for cls in (paramiko.Ed25519Key, paramiko.RSAKey, paramiko.ECDSAKey):
try:
return cls.from_private_key(io.StringIO(content))
except Exception:
pass
raise ValueError("Неподдерживаемый тип SSH-ключа")
def _ssh_connect(retries=5, delay=10):
key = _load_key(os.environ["SSH_KEY"])
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
last_err = None
for attempt in range(retries):
try:
client.connect(
hostname=os.environ["VM_IP"],
username=os.environ["SSH_USER"],
pkey=key,
timeout=15,
)
return client
except Exception as e:
last_err = e
if attempt < retries - 1:
time.sleep(delay)
raise RuntimeError(f"SSH не удалось после {retries} попыток: {last_err}")
def _run(client, cmd, timeout=120, check=True):
_, stdout, stderr = client.exec_command(cmd, timeout=timeout)
code = stdout.channel.recv_exit_status()
out = stdout.read().decode(errors="replace").strip()
err = stderr.read().decode(errors="replace").strip()
if check and code != 0:
raise RuntimeError(f"Ошибка (exit {code}):\n{cmd}\nstderr: {err}")
return code, out, err
def _wait_apt_lock(client, attempts=20, delay=10):
"""Ждать завершения cloud-init и убить авто-обновления. Ubuntu 22.04+."""
# Шаг 1: Ждём завершения cloud-init — он держит apt при первом старте VM
_run(client, "timeout 300 sudo cloud-init status --wait 2>/dev/null; true", check=False, timeout=310)
# Шаг 2: Mask (не просто disable) — systemd не сможет перезапустить
_run(client, "sudo systemctl mask unattended-upgrades apt-daily.service apt-daily-upgrade.service apt-daily.timer apt-daily-upgrade.timer 2>/dev/null; true", check=False)
_run(client, "sudo systemctl stop unattended-upgrades apt-daily.service apt-daily-upgrade.service 2>/dev/null; true", check=False)
# Шаг 3: Добить оставшиеся apt/dpkg процессы
_run(client, "sudo pkill -9 -x unattended-upgrades apt-get apt dpkg 2>/dev/null; true", check=False)
_run(client, "sudo kill -9 $(sudo lsof -t /var/lib/dpkg/lock-frontend 2>/dev/null) 2>/dev/null; true", check=False)
# Шаг 4: Убрать стейл-локи и починить dpkg
_run(client, "sudo rm -f /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock /var/lib/apt/lists/lock 2>/dev/null; true", check=False)
_run(client, "sudo dpkg --configure -a 2>/dev/null; true", check=False)
time.sleep(3)
locks = ["/var/lib/dpkg/lock-frontend", "/var/lib/dpkg/lock", "/var/lib/apt/lists/lock"]
for i in range(attempts):
all_free = all(
_run(client, f"sudo flock -n {lock} true 2>/dev/null", check=False)[0] == 0
for lock in locks
)
if all_free:
return
_run(client, "sudo pkill -9 -x apt-get apt dpkg 2>/dev/null; true", check=False)
_run(client, "sudo kill -9 $(sudo lsof -t /var/lib/dpkg/lock-frontend 2>/dev/null) 2>/dev/null; true", check=False)
if i < attempts - 1:
time.sleep(delay)
raise RuntimeError("apt lock занят слишком долго — проверьте процессы на ВМ")
def install(event):
"""Установить nginx. Если уже установлен — проверить что запущен."""
client = _ssh_connect()
try:
# Проверить: уже установлен?
code, ver_out, _ = _run(client, "nginx -v 2>&1", check=False)
already_installed = "nginx version" in ver_out
if already_installed:
# Убедиться что сервис запущен
_run(client, "sudo systemctl start nginx", check=False)
version = ver_out.replace("nginx version: nginx/", "").strip()
_, http_code, _ = _run(
client, "curl -s -o /dev/null -w '%{http_code}' http://localhost", check=False
)
return {
"status": "already_installed",
"version": version,
"http_check": http_code,
}
_wait_apt_lock(client)
_run(client, "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 update -qq", timeout=420)
_run(
client,
"sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 install -y -qq nginx",
timeout=300,
)
_run(client, "sudo systemctl enable nginx")
_run(client, "sudo systemctl start nginx")
# Проверить HTTP-ответ на localhost
_, http_code, _ = _run(
client, "curl -s -o /dev/null -w '%{http_code}' http://localhost", check=False
)
_, ver_out, _ = _run(client, "nginx -v 2>&1", check=False)
version = ver_out.replace("nginx version: nginx/", "").strip()
return {
"status": "ok",
"version": version,
"http_check": http_code,
}
finally:
client.close()
@@ -0,0 +1,2 @@
paramiko
# v6
@@ -0,0 +1,133 @@
# 2026-03-29 — handler.py: установка apt-пакетов на ВМ по SSH.
# sless_job runtime: python3.11, entrypoint: handler.install
#
# event_json:
# packages: ["git", "curl", ...] — список пакетов (обязательно)
# update: true/false — apt-get update перед install (default: true)
#
# env_vars:
# VM_IP: внешний IP ВМ
# SSH_USER: логин (ubuntu)
# SSH_KEY: содержимое приватного SSH-ключа (PEM)
import os, io, time
import paramiko
def _load_key(content):
"""Загрузить SSH-ключ (Ed25519 / RSA / ECDSA)."""
for cls in (paramiko.Ed25519Key, paramiko.RSAKey, paramiko.ECDSAKey):
try:
return cls.from_private_key(io.StringIO(content))
except Exception:
pass
raise ValueError("Неподдерживаемый тип SSH-ключа")
def _ssh_connect(retries=5, delay=10):
"""Подключение к ВМ с retry — ВМ может ещё загружаться."""
key = _load_key(os.environ["SSH_KEY"])
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
last_err = None
for attempt in range(retries):
try:
client.connect(
hostname=os.environ["VM_IP"],
username=os.environ["SSH_USER"],
pkey=key,
timeout=15,
)
return client
except Exception as e:
last_err = e
if attempt < retries - 1:
time.sleep(delay)
raise RuntimeError(f"SSH не удалось после {retries} попыток: {last_err}")
def _run(client, cmd, timeout=120, check=True):
"""Выполнить команду, вернуть (exit_code, stdout, stderr)."""
_, stdout, stderr = client.exec_command(cmd, timeout=timeout)
code = stdout.channel.recv_exit_status()
out = stdout.read().decode(errors="replace").strip()
err = stderr.read().decode(errors="replace").strip()
if check and code != 0:
raise RuntimeError(f"Ошибка (exit {code}):\n{cmd}\nstderr: {err}")
return code, out, err
def _wait_apt_lock(client, attempts=20, delay=10):
"""Ждать завершения cloud-init и убить авто-обновления. Ubuntu 22.04+."""
# Шаг 1: Ждём завершения cloud-init — он держит apt при первом старте VM
_run(client, "timeout 300 sudo cloud-init status --wait 2>/dev/null; true", check=False, timeout=310)
# Шаг 2: Mask (не просто disable) — systemd не сможет перезапустить
_run(client, "sudo systemctl mask unattended-upgrades apt-daily.service apt-daily-upgrade.service apt-daily.timer apt-daily-upgrade.timer 2>/dev/null; true", check=False)
_run(client, "sudo systemctl stop unattended-upgrades apt-daily.service apt-daily-upgrade.service 2>/dev/null; true", check=False)
# Шаг 3: Добить оставшиеся apt/dpkg процессы
_run(client, "sudo pkill -9 -x unattended-upgrades apt-get apt dpkg 2>/dev/null; true", check=False)
_run(client, "sudo kill -9 $(sudo lsof -t /var/lib/dpkg/lock-frontend 2>/dev/null) 2>/dev/null; true", check=False)
# Шаг 4: Убрать стейл-локи и починить dpkg
_run(client, "sudo rm -f /var/lib/dpkg/lock-frontend /var/lib/dpkg/lock /var/cache/apt/archives/lock /var/lib/apt/lists/lock 2>/dev/null; true", check=False)
_run(client, "sudo dpkg --configure -a 2>/dev/null; true", check=False)
time.sleep(3)
locks = [
"/var/lib/dpkg/lock-frontend",
"/var/lib/dpkg/lock",
"/var/lib/apt/lists/lock",
]
for i in range(attempts):
all_free = all(
_run(client, f"sudo flock -n {lock} true 2>/dev/null", check=False)[0] == 0
for lock in locks
)
if all_free:
return
# Повторить убийство процессов удерживающих lock
_run(client, "sudo pkill -9 -x apt-get apt dpkg 2>/dev/null; true", check=False)
_run(client, "sudo kill -9 $(sudo lsof -t /var/lib/dpkg/lock-frontend 2>/dev/null) 2>/dev/null; true", check=False)
if i < attempts - 1:
time.sleep(delay)
raise RuntimeError("apt lock занят слишком долго — проверьте процессы на ВМ")
def install(event):
"""Установить apt-пакеты. Идемпотентно — повторный запуск безопасен."""
packages = event.get("packages", [])
if not packages:
return {"status": "skipped", "reason": "packages list is empty"}
do_update = event.get("update", True)
client = _ssh_connect()
try:
_wait_apt_lock(client)
if do_update:
_run(client, "sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 update -qq", timeout=420)
pkg_str = " ".join(packages)
_run(
client,
f"sudo DEBIAN_FRONTEND=noninteractive apt-get -o DPkg::Lock::Timeout=600 install -y -qq {pkg_str}",
timeout=300,
)
# Проверить что установилось
installed, missing = [], []
for pkg in packages:
code, _, _ = _run(
client,
f"dpkg -l {pkg} 2>/dev/null | grep -q '^ii'",
check=False,
)
(installed if code == 0 else missing).append(pkg)
return {
"status": "ok" if not missing else "partial",
"installed": installed,
"missing": missing,
}
finally:
client.close()
@@ -0,0 +1,2 @@
paramiko
# v6
+5 -1
View File
@@ -5,7 +5,11 @@ terraform {
required_providers {
nubes = {
source = "terra.k8c.ru/nubes/nubes"
version = "5.0.49"
version = "5.0.51"
}
sless = {
source = "terra.k8c.ru/naeel/sless"
version = "~> 0.1"
}
}
}
+18
View File
@@ -0,0 +1,18 @@
# 2026-03-29 — outputs.tf: результаты установки ПО на ВМ.
# phase: Pending / Building / Running / Succeeded / Failed
# message: JSON с деталями (что установлено) или traceback при ошибке
output "install_packages_result" {
description = "Результат установки базовых пакетов"
value = var.install_packages ? sless_job.install_packages[0].message : "skipped"
}
output "install_nginx_result" {
description = "Результат установки nginx"
value = var.install_nginx ? sless_job.install_nginx[0].message : "skipped"
}
output "install_docker_result" {
description = "Результат установки Docker"
value = var.install_docker ? sless_job.install_docker[0].message : "skipped"
}
+108
View File
@@ -0,0 +1,108 @@
# 2026-03-29 — sless.tf: провайдер sless и sless_job ресурсы для установки ПО на ВМ.
#
# Схема работы:
# 1. terraform apply создаёт FunctionJob CR в k8s
# 2. Провайдер загружает код из source_dir в S3
# 3. Оператор собирает Docker-образ (kaniko) и запускает Job
# 4. Job подключается к ВМ по SSH и устанавливает ПО
# 5. terraform apply завершается: outputs содержат статус каждого шага
#
# Для повторного запуска: увеличь install_run_id в terraform.tfvars → terraform apply
# ---------------------------------------------------------------------------
# Провайдер
# ---------------------------------------------------------------------------
provider "sless" {
endpoint = "https://sless.kube5s.ru"
token = var.api_token # тот же JWT что и в provider "nubes"
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm"
}
# ---------------------------------------------------------------------------
# Общие locals: SSH-параметры для подключения к ВМ
# ---------------------------------------------------------------------------
locals {
# TODO: заменить externalConnect → internalConnect когда DevOps настроят
# сеть между k8s кластером и Nubes vDC (сейчас только внешний IP доступен).
vm_ip = nubes_vc_vm_v3.vm.state_out_flat["externalConnect"]
ssh_env = {
VM_IP = local.vm_ip
SSH_USER = "ubuntu"
# TODO(vault): заменить на чтение из Vault когда сервис заработает; пока тестовый стенд — прямой файл.
SSH_KEY = file("${path.module}/vm_key")
}
}
# ---------------------------------------------------------------------------
# Job 1: базовые пакеты (jq, pip3 и др.)
# ---------------------------------------------------------------------------
resource "sless_job" "install_packages" {
count = var.install_packages ? 1 : 0
name = "vm-install-packages"
runtime = "python3.11"
entrypoint = "handler.install"
source_dir = "${path.module}/functions/install-packages"
memory_mb = 128
env_vars = local.ssh_env
event_json = jsonencode({
packages = var.base_packages
update = true
})
run_id = var.install_run_id
wait_timeout_sec = 600
depends_on = [nubes_vc_vm_v3.vm]
}
# ---------------------------------------------------------------------------
# Job 2: nginx
# ---------------------------------------------------------------------------
resource "sless_job" "install_nginx" {
count = var.install_nginx ? 1 : 0
name = "vm-install-nginx"
runtime = "python3.11"
entrypoint = "handler.install"
source_dir = "${path.module}/functions/install-nginx"
memory_mb = 128
env_vars = local.ssh_env
event_json = jsonencode({})
run_id = var.install_run_id
wait_timeout_sec = 600
depends_on = [nubes_vc_vm_v3.vm, sless_job.install_packages]
}
# ---------------------------------------------------------------------------
# Job 3: Docker CE
# ---------------------------------------------------------------------------
resource "sless_job" "install_docker" {
count = var.install_docker ? 1 : 0
name = "vm-install-docker"
runtime = "python3.11"
entrypoint = "handler.install"
source_dir = "${path.module}/functions/install-docker"
memory_mb = 128
env_vars = local.ssh_env
event_json = jsonencode({
compose = true
})
run_id = var.install_run_id
wait_timeout_sec = 900
depends_on = [nubes_vc_vm_v3.vm, sless_job.install_packages, sless_job.install_nginx]
}
+37
View File
@@ -0,0 +1,37 @@
# 2026-03-29 — variables.tf: переменные для sless и установки ПО на ВМ.
# Переменные nubes (api_token, vm_public_key) остаются в main.tf.
# sless использует тот же api_token — отдельной переменной не нужно.
# ---- Флаги: что устанавливать на ВМ --------------------------------------
variable "install_packages" {
type = bool
default = true
description = "Установить базовые apt-пакеты (jq и др.)"
}
variable "install_nginx" {
type = bool
default = false
description = "Установить nginx"
}
variable "install_docker" {
type = bool
default = false
description = "Установить Docker CE + docker-compose-plugin"
}
# ---- Параметры ------------------------------------------------------------
variable "base_packages" {
type = list(string)
default = ["jq", "python3-pip", "htop", "unzip"]
description = "Список apt-пакетов для install-packages"
}
variable "install_run_id" {
type = number
default = 1
description = "Увеличь на 1 чтобы запустить все install-джобы заново"
}
+9 -7
View File
@@ -3,19 +3,21 @@
// image_vm, vapp_uid, user_public_key не изменяются после создания.
resource "nubes_vc_vm_v3" "vm" {
resource_name = "vm-sless"
resource_name = "vm-sless-1"
vm_name = "web02" # Имя ВМ в Nubes vCD. Не изменяется после создания.
# Определяет имя NSX-T IP Set: {vapp_name}-{vm_name}
vapp_uid = nubes_vapp.vapp.id # ссылка на vApp. Не изменяется после создания.
image_vm = "Ubuntu_22-20G" # Не изменяется после создания.
# image_vm = "Ubuntu 22.04 LTS" # Не изменяется после создания.
vapp_uid = nubes_vapp.vapp.id # ссылка на vApp. Не изменяется после создания.
image_vm = "Ubuntu_22-20G" # Не изменяется после создания.
# image_vm = "Ubuntu 22.04 LTS" # Не изменяется после создания.
ip_space_name = "internet-ipv4-v1"
user_login = "ubuntu"
user_public_key = var.vm_public_key # задаётся в terraform.tfvars
user_public_key = var.vm_public_key # задаётся в terraform.tfvars
vm_cpu = 2
vm_ram = 2 # GB
vm_disk = 20 # GB
vm_ram = 2 # GB
vm_disk = 20 # GB
adopt_existing_on_create = true
operation_timeout = "15m"