Хочешь быстро поднять несколько лёгких Linux-виртуалок из Windows, да так, чтобы процесс можно было легко повторить? Я, как любитель и самоучка, недавно решил с этим разобраться и всё получилось.
Я делал это на Windows через WSL, куда установил лёгкий Alpine-minirootfs, а после Ansible с помощью которого автоматически создал в Oracle VirtualBox 3 виртуальные машины на базе Alpine-standard. Никаких облаков, только локальный контроль и минимум зависимостей. Для чего всё это? Сейчас всё чаще говорят о минималистичных системах, об оптимизации железа под конкретные задачи, о работе на граничных устройствах, вот и захотелось попробовать всё своими руками.
Введение
Автоматизация развертывания ВМ (виртуальных машин) это не только про «большие» CI/CD-пайплайны в облаке. Иногда нужно быстро поднять локальное окружение у себя для тестов, экспериментов с сетью, обучения или демонстрации. И хочется сделать это так, чтобы легко повторить самому и поделиться с коллегой.
Почему это может быть полезно?
Повторяемость и декларативность при помощи Ansible. Можно положить в репозиторий и коллеги воспроизведут окружение точь-в-точь.
Минимальный вес на базе Alpine который быстро грузится, поэтому идеально подходит для локальных тест-кластеров.
Работа через WSL на Windows для исполнения Ansible даёт привычный Linux-инструментарий и удобство автоматизации.
Локальный контроль, нет облаков. Быстро, дешево и удобно для прототипов.
Пример без «воды» - только то, что реально работает
Подготовка среды
Ansible нельзя запускать как управляющую машину из Windows напрямую, поэтому общая схема будет такой:
Windows → запускает WSL → устанавливаем Alpine → устанавливаем Ansible → обращаемся к VirtualBox → создаём ВМ на базе Alpine.
То есть:
VirtualBox остаётся установленным в Windows;
Ansible запускаем внутри WSL (Alpine minirootfs);
Управляем VirtualBox через VBoxManage, доступный из Windows, но вызываемый из WSL.
Вроде звучит всё достаточно просто и можно приступать сразу к действию. Открываем терминал, и если это Git bash, то:

Важно выполнять команды внутри WSL, например в Alpine, которую можно установить так:
Win → вводим "PowerShell" → Enter
Проверяем, есть ли установленные дистрибутивы
wsl --list --online

Если WSL установлен, то увидим список доступных дистрибутивов, иначе WSL НЕ установлен и покажет сообщение об установке.
Так как WSL это как виртуальная машина без установленной ОС, то необходимо выбрать и установить дистрибутив Linux. Если у вас много свободного места на диске, то можно выбрать любую версию и установить так:
wsl --install -d Ubuntu-22.04
После установки появится новое окно Ubuntu (это будет настоящее Linux окружение). Далее необходимо поставить Ansible командой:
sudo apt update
sudo apt install ansible -y
ansible --version
На мой взгляд это слишком просто и не интересно. Усложняем задачу:
У нас мало места.
Нужна конкретная версия Linux, которая недоступна в Microsoft Store.
Хм…. Alpine отлично подходит под оба пункта. Я часто использовал его в Docker, поэтому выбрал и здесь. Варианты установки:
I. Установка Alpine из официального rootfs.
по ссылке alpinelinux скачиваем файл вида: alpine-minirootfs-3.22.2-x86_64.tar.gz;
создаем директорию, куда установим Alpine командой
mkdir C:\wsl\alpine;установим командой
wsl --import Alpine C:\wsl\alpine C:\Users\{Ваше имя}\Downloads\alpine-minirootfs-3.22.2-x86_64.tar.gz;запустим командой
wsl -d Alpine.

Если видите нечто подобное, то Alpine в WSL развернут. Вроде всё хорошо, но нужно помнить, что малый вес Alpine сопровождается минусами, а именно Alpine = musl libc, а Ansible требует glibc для некоторых модулей. Отсюда следует, что работает не всё:
нет systemd;
часть модулей ansible не работает;
требуется установка python3, py3-stdlib, py3-pip и это всё вручную.
Устанавливаем Ansible в Alpine следующими командами:
apk update
apk add python3 py3-pip py3-cryptography openssh-client
pip install ansible

Мы работаем в Alpine Linux, и pip НЕ разрешает устанавливать пакеты в системную Python-среду, потому что Alpine использует менеджер пакетов apk, и система запрещает pip менять системный Python, чтобы ничего не сломать.
нельзя ставить Ansible через pip напрямую
Варианты решения:
1. Создаем виртуальное окружение командами:
установим нужные пакеты
apk add python3 py3-pip py3-virtualenv;создаем venv
python3 -m venv ~/ansible-venv;активируем его
. ~/ansible-venv/bin/activate;ставим Ansible
pip install ansible.
2. Используем apk (если пакет есть).
в Alpine есть пакет ansible, но он не обновлялся в старых релизах. Проверяем это командой:
apk add ansible.
3. Принудительно игнорировать защиту (НЕ рекомендую).
можно заставить pip установить в системную директорию командой
pip install ansible --break-system-packages.

II. Установка Alpine из Microsoft Store (если появится)
иногда Alpine возвращают. Можно попробовать установить командой:
wsl --install -d Alpine.
После установки проверяем, что Ansible установлен командой:
ansible –version
Если всё корректно, то увидим версию и путь к python.
Проверим, что Alpine «видит» VM VirtualBox. В Windows VirtualBox обычно распологается по адресу C:\Program Files\Oracle\VirtualBox\VBoxManage.exe. А в WSL путь будет /mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe. Это можно проверить командой:
"/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe" --version
в результате которой отобразится номер версии.
Создаем симлинк (symlink, soft link), чтобы работать удобнее командой:
sudo ln -s "/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe" /usr/local/bin/vboxmanage
или
ln -s "/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe" /usr/local/bin/vboxmanage
Проверяем это командой:
vboxmanage --version

Почему sudo не найден? Потому что в Alpine Linux по умолчанию нет sudo. В Alpine используется doas (аналог sudo), но и его по умолчанию тоже нет. Я создал симлинк без sudo, и это сработало, потому что:
/usr/local/bin принадлежит root;
работаем, скорее всего, в WSL Alpine под root, что подтверждает это введение команды whoami и вывод - root.
Создаем 3 ВМ Alpine Linux
Сначала ещё раз убедимся, что VirtualBox действительно доступен из WSL. Для этого используем команду:
vboxmanage list vms
Это та самая команда, на которую мы сделали симлинк раньше. Если всё настроено правильно, вы увидите список существующих ВМ (или пустой список, если их ещё нет).

Ansible напрямую не умеет создавать ВМ в VirtualBox. Но это не проблема, у нас есть VBoxManage, и его можно вызывать через модули command или shell. Такой подход отлично работает во всех случаях, когда готовых Ansible-модулей нет.

Если вы ещё не знакомы с vi и не хотите мучиться, то установите привычный редактор командой:
apk add nano или apk add vim
Не все каталоги Windows доступны WSL для записи, особенно «защищённые». Поэтому надёжнее всего создавать файлы в домашней директории WSL
cd ~
nano create_alpine_vms.yml
А после создания можно переместить файл в /mnt/c/..., если нужно:
cp create_alpine_vms.yml /mnt/c/
Важный момент, VirtualBox должен видеть ISO в Windows, а не в WSL. VirtualBox это Windows-приложение, поэтому он не может открыть ISO, лежащее внутри WSL-файловой системы (/home/... или /root/...). Если передать такой путь, VirtualBox выдаст ошибку доступа.
Чтобы избежать этого, создадим отдельную директорию в Windows, где будем хранить ISO:
C:\Users\{Имя}\VirtualBox_ISO #пример пути
Скопируем через UI или командой:
cp /mnt/c/Users/{Имя}/Downloads/alpine-standard-3.22.2-x86_64.iso /mnt/c/Users/{Имя}/VirtualBox_ISO/
VirtualBox создаёт все файлы ВМ строго в Windows. Поэтому сразу подготовим папку:
C:\Users\{Имя}\VirtualBox_VMs #пример пути
Создать её можно и из WSL:
mkdir -p /mnt/c/Users/{Имя}/VirtualBox_ISO
mkdir -p /mnt/c/Users/{Имя}/VirtualBox_VMs
Если ISO у вас лежит в Downloads, можно просто скопировать его в нужную директорию:
cp /mnt/c/Users/{Имя}/Downloads/alpine-standard-3.22.2-x86_64.iso /mnt/c/Users/{Имя}/VirtualBox_ISO/alpine-standard-3.22.2-x86_64.iso
Чтобы убедиться, что всё работает, вызываем:
"/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe" --version
Пример playbook (create_alpine_vms.yml) для создания 3 ВМ Alpine Linux:
Скрытый текст
- name: Create 3 Alpine VMs with autoinstall ISO and SSH port forwarding
hosts: localhost
connection: local
gather_facts: no
vars:
vm_count: 3
vm_memory: 512
vm_cpus: 1
vm_disk: 2000 # MB
orig_iso_win: "C:/Users/{Имя}/VirtualBox_ISO/alpine-standard-3.22.2-x86_64.iso"
target_iso_win: "C:/Users/{Имя}/VirtualBox_ISO/alpine-autoinstall.iso"
orig_iso_wsl: "/mnt/c/Users/{Имя}/VirtualBox_ISO/alpine-standard-3.22.2-x86_64.iso"
target_iso_wsl: "/mnt/c/Users/{Имя}/VirtualBox_ISO/alpine-autoinstall.iso"
workdir: "/tmp/alpine_iso_build"
vm_base_path_win: "C:/Users/{Имя}/VirtualBox_VMs"
vboxmanage: "/mnt/c/Program Files/Oracle/VirtualBox/VBoxManage.exe"
base_ssh_port: 2220
root_password: "alpine"
tasks:
- name: Ensure workdir exists
file:
path: "{{ workdir }}"
state: directory
- name: Fail if original ISO missing
stat:
path: "{{ orig_iso_wsl }}"
register: origiso_stat
- name: Abort if ISO not found
fail:
msg: "Original ISO not found at {{ orig_iso_wsl }}"
when: not origiso_stat.stat.exists
- name: Clean workdir
file:
path: "{{ workdir }}"
state: absent
- name: Create empty workdir
file:
path: "{{ workdir }}"
state: directory
- name: Extract original ISO contents
shell: |
xorriso -osirrox on -indev "{{ orig_iso_wsl }}" -extract / "{{ workdir }}"
args:
executable: /bin/sh
- name: Add autoinstall answers file
copy:
dest: "{{ workdir }}/answers"
mode: '0644'
content: |
us
us
no
no
eth0
dhcp
alpine-host
root
{{ root_password }}
{{ root_password }}
yes
sys
/dev/sda
yes
yes
yes
openssh
yes
- name: Repack ISO with autoinstall
shell: |
rm -f "{{ target_iso_wsl }}" || true
cd "{{ workdir }}"
xorriso -as mkisofs \
-o "{{ target_iso_wsl }}" \
-R -J -V "ALPINE_AUTOINSTALL" \
-b boot/syslinux/isolinux.bin \
-c boot/syslinux/boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
.
args:
executable: /bin/sh
- name: Fail if autoinstall ISO not created
stat:
path: "{{ target_iso_wsl }}"
register: targetiso_stat
- name: Abort if autoinstall ISO missing
fail:
msg: "Autoinstall ISO was not created at {{ target_iso_wsl }}"
when: not targetiso_stat.stat.exists
- name: Remove old VMs if they exist
shell: |
if "{{ vboxmanage }}" list vms | grep -q "alpine{{ item }}"; then
"{{ vboxmanage }}" controlvm "alpine{{ item }}" poweroff 2>/dev/null || true
sleep 2
"{{ vboxmanage }}" unregistervm "alpine{{ item }}" --delete 2>/dev/null || true
fi
loop: "{{ range(1, vm_count + 1) | list }}"
args:
executable: /bin/sh
- name: Create VM directories on Windows
shell: |
mkdir -p "{{ vm_base_path_win | replace('C:', '/mnt/c') }}/alpine{{ item }}"
loop: "{{ range(1, vm_count + 1) | list }}"
args:
executable: /bin/sh
- name: Create VMs
shell: |
"{{ vboxmanage }}" createvm --name alpine{{ item }} --ostype Linux_64 --register
loop: "{{ range(1, vm_count + 1) | list }}"
args:
executable: /bin/sh
- name: Configure VM (RAM/CPU/NAT/boot)
shell: |
"{{ vboxmanage }}" modifyvm alpine{{ item }} \
--memory {{ vm_memory }} \
--cpus {{ vm_cpus }} \
--nic1 nat \
--natpf1 "guestssh,tcp,,{{ base_ssh_port + item }},,22" \
--boot1 dvd --boot2 disk --boot3 none --boot4 none
loop: "{{ range(1, vm_count + 1) | list }}"
args:
executable: /bin/sh
- name: Create virtual disk
shell: |
"{{ vboxmanage }}" createmedium disk \
--filename "{{ vm_base_path_win }}/alpine{{ item }}/disk.vdi" \
--size {{ vm_disk }} --format VDI
loop: "{{ range(1, vm_count + 1) | list }}"
args:
executable: /bin/sh
- name: Attach SATA controller and disk
shell: |
"{{ vboxmanage }}" storagectl alpine{{ item }} --name "SATA Controller" --add sata --controller IntelAhci
"{{ vboxmanage }}" storageattach alpine{{ item }} --storagectl "SATA Controller" --port 0 --device 0 --type hdd --medium "{{ vm_base_path_win }}/alpine{{ item }}/disk.vdi"
loop: "{{ range(1, vm_count + 1) | list }}"
args:
executable: /bin/sh
- name: Attach IDE controller and autoinstall ISO
shell: |
"{{ vboxmanage }}" storagectl alpine{{ item }} --name "IDE Controller" --add ide
"{{ vboxmanage }}" storageattach alpine{{ item }} --storagectl "IDE Controller" --port 0 --device 0 --type dvddrive --medium "{{ target_iso_win }}"
loop: "{{ range(1, vm_count + 1) | list }}"
args:
executable: /bin/sh
- name: Start VMs headless
shell: |
"{{ vboxmanage }}" startvm alpine{{ item }} --type headless
loop: "{{ range(1, vm_count + 1) | list }}"
args:
executable: /bin/sh
- name: Wait for SSH to be available
wait_for:
host: localhost
port: "{{ base_ssh_port + item }}"
timeout: 180
delay: 10
loop: "{{ range(1, vm_count + 1) | list }}"
ignore_errors: yes
- name: Display SSH info
debug:
msg: "alpine{{ item }} -> ssh root@localhost -p {{ base_ssh_port + item }} (password: {{ root_password }})"
loop: "{{ range(1, vm_count + 1) | list }}"
При написании плейбука важно учитывать архитектуру среды, особенно если она гибридная, как у меня(Windows + WSL + Alpine Linux).
Shell в Alpine Linux отличается от привычного bash. В Alpine по умолчанию нет bash. Вместо него используется sh (конкретно ash).
Вместо:
args:
executable: /bin/bash
Должно быть:
args:
executable: /bin/sh
Если вы всё же хотите использовать bash, его нужно установить вручную:
apk add bash
Но в большинстве случаев sh достаточно для задач автоматизации.
Иногда удобнее запускать Ansible не из WSL, а прямо из Windows PowerShell (например, когда Ansible установлен через Cygwin или WSL подключается автоматически через wsl.exe).
В таком случае команды нужно выполнять через PowerShell, и Ansible должен вызывать именно его. Тогда в плейбуке указываем:
args:
executable: powershell.exe

Команда vboxmanage list vms позволит вывести список всех VM.

Если вы видите, что контроллеры SATA/IDE уже существуют и VirtualBox не может добавить контроллер с таким именем, если они уже есть, то их необходимо удалить.
Команда для удаления:
vboxmanage unregistervm "alpine1" --delete
Результат
По результату успешной работы будет создано 3 VM в VirtualBox с определенными параметрами, в том числе Port Forwarding для SSH:
NAT-сеть (--nic1 nat);
Порт на хосте: 2221, 2222, 2223 → порт 22 в VM
В результате Ansible может подключаться к VM через локальный порт без ручной настройки сети.
Для проверки можно использовать команду:
vboxmanage showvminfo alpine1 | grep "Forwarding"
Пример вывода: NIC 1 Rule(0): name = guestssh, protocol = TCP, host port = 2221, guest port = 22
В процессе автоматической установки (autoinstall) плейбук подсовывает файл answers, поэтому Alpine настраивается полностью без участия пользователя. Эта процедура:
создает пользователя vagrant с паролем vagrant;
устанавливает openssh;
подключаются VirtualBox Guest Additions;
система настраивается на часовой пояс UTC.
Эти действия можно расширить или изменить через файл answers, если потребуется.
Перед выполнением плейбука полезно убедиться, что в YAML нет ошибок. Для этого есть стандартная команда:

Запуск плейбука осуществляется командой:
ansible-playbook create_alpine_vms.yml

После выполнения плейбука в VirtualBox должны появиться три новые виртуальные машины. Чтобы убедиться в этом, откроем VirtualBox и увидим свежесозданные ВМ в списке.

В превью VirtualBox можно увидеть их текущий статус развертки с зелёным индикатором OK, что подтверждает успешное развертывание.

РИ Рисунок 15. Вводим login для завершения установки
При открытии окна виртуальной машины появится стандартное окно входа в систему, поле для ввода login. Это означает, что установка прошла успешно и Alpine полностью загружен.
Выбор инструмента зависит от конкретных потребностей и сценариев использования, и для управления инфраструктурой как код (IaC) есть отличный инструмент, - Terraform. А Ansible же больше подходит для управление конфигурацией уже существующей инфраструктуры, но тем не менее и он может.
Иногда достаточно одного плейбука, чтобы за несколько минут получить готовые виртуальные машины, с которыми можно экспериментировать и делиться результатами.