Хочешь быстро поднять несколько лёгких 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, то:

Рисунок 1. Пример запуска wsl в Git Bash.
Рисунок 1. Пример запуска wsl в Git Bash.

Важно выполнять команды внутри WSL, например в Alpine, которую можно установить так:

Win → вводим "PowerShell" → Enter

Проверяем, есть ли установленные дистрибутивы

wsl --list --online
Рисунок 2. Пример запуска wsl в PowerShell.
Рисунок 2. Пример запуска wsl в PowerShell.

Если WSL установлен, то увидим список доступных дистрибутивов, иначе WSL НЕ установлен и покажет сообщение об установке.

Так как WSL это как виртуальная машина без установленной ОС, то необходимо выбрать и установить дистрибутив Linux. Если у вас много свободного места на диске, то можно выбрать любую версию и установить так:

wsl --install -d Ubuntu-22.04 

После установки появится новое окно Ubuntu (это будет настоящее Linux окружение). Далее необходимо поставить Ansible командой:

sudo apt update
sudo apt install ansible -y
ansible --version

На мой взгляд это слишком просто и не интересно. Усложняем задачу:

  1. У нас мало места.

  2. Нужна конкретная версия 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.

Рисунок 3. Результат запуска Alpine в WSL
Рисунок 3. Рез��льтат запуска Alpine в WSL

Если видите нечто подобное, то 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
Рисунок 4. Пример ошибки установки ansible
Рисунок 4. Пример ошибки установки 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.

Рисунок 5. Установка Ansible методом №2
Рисунок 5. Установка Ansible методом №2

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
Рисунок 6.  Пример вывода проверки симлинк
Рисунок 6.  Пример вывода проверки симлинк

Почему 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

Это та самая команда, на которую мы сделали симлинк раньше. Если всё настроено правильно, вы увидите список существующих ВМ (или пустой список, если их ещё нет).

Рисунок 7. Пример вывода развернутых ВМ
Рисунок 7. Пример вывода развернутых ВМ

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

Рисунок 8. Пример вывода при создании файла playbook
Рисунок 8. Пример вывода при создании файла playbook

Если вы ещё не знакомы с 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
Рисунок 10. Пример дебага ошибки наличия bash и sh
Рисунок 10. Пример дебага ошибки наличия bash и sh

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

Рисунок 11. Пример вывода команды «Список всех VM»
Рисунок 11. Пример вывода команды «Список всех 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 нет ошибок. Для этого есть стандартная команда:

Рисунок 12. Пример работы с проверкой синтаксиса файла playbook
Рисунок 12. Пример работы с проверкой синтаксиса файла playbook

Запуск плейбука осуществляется командой:

ansible-playbook create_alpine_vms.yml
Рисунок 13. Пример вывода результата работы playbook
Рисунок 13. Пример вывода результата работы playbook

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

Рисунок 14. Проверка развернутых систем
Рисунок 14. Проверка развернутых систем

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

Рисунок 15. Вводим login для завершения установки
Рисунок 15. Вводим login для завершения установки

РИ Рисунок 15. Вводим login для завершения установки

При открытии окна виртуальной машины появится стандартное окно входа в систему, поле для ввода login. Это означает, что установка прошла успешно и Alpine полностью загружен.

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

Иногда достаточно одного плейбука, чтобы за несколько минут получить готовые виртуальные машины, с которыми можно экспериментировать и делиться результатами.

Комментарии (0)