Перейти к содержимому

Загрузка собственного Windows-образа

Вы можете загрузить собственный пользовательский образ Windows Server в формате qcow2, vmdk или raw. Этот образ можно использовать для создания ВМ.

Примечание

Рекомендованные форматы образов — qcow2 и vmdk. Используйте формат raw только в сценариях, в которых рекомендованные форматы не поддерживается. Образы в форматах raw, qcow, vhd, vhdx и других форматах, поддерживаемых QEMU, можно конвертировать в qcow2 с помощью утилиты qemu-img.

Для загрузки образа:

  1. Подготовьте окружение для работы с образами.
  2. Подготовьте образ для использования в MWS Cloud.
  3. Установите и настройте утилиту AWS CLI. С ее помощью образ будет загружен с локального компьютера или ВМ в бакет Object Storage.

В результате образ будет доступен в качестве источника при создании новой ВМ.

1. Подготовьте окружение для работы с образами

Заголовок раздела «1. Подготовьте окружение для работы с образами»

В этом руководстве даны инструкции для компьютера на базе архитектуры x86-64 с ОС Ubuntu 22.04.

  1. Обновите уже установленные пакеты:

    bash
    sudo apt update && sudo apt upgrade
  2. Установите необходимые пакеты:

    bash
    sudo apt install \
    qemu-system \
    ovmf \
    libguestfs-tools \
    virt-viewer \
    unzip

    Здесь:

    • ovmf — система UEFI OVMF;
    • libguestfs-tools — набор утилит для взаимодействия с ВМ;
    • virt-viewer — клиент для подключения к ВМ по протоколу SPICE;
    • unzip — утилита для распаковки zip-архивов.
  3. Добавьте текущего пользователя в группу kvm:

    bash
    sudo usermod -aG kvm $USER

2. Создайте и запустите виртуальную машину

Заголовок раздела «2. Создайте и запустите виртуальную машину»
  1. Создайте в домашней директории каталоги для исходных iso-образов и qcow2-образа Windows Server:

    bash
    cd ~ && mkdir iso out
  2. Загрузите образ нужной версии Windows Server с официального сайта Microsoft:

    Важно

    В облаке будут работать только образы с английской локализацией. Вы можете установить дополнительные языки после первого входа в систему и смены пароля пользователя.

    bash
    curl -L -o iso/windows.iso "<ссылка на образ>"
  3. Загрузите пакет драйверов Virtio:

    bash
    curl -L -o iso/virtio-win.iso "https://fedora-virt.repo.nfrance.com/virtio-win/direct-downloads/stable-virtio/virtio-win-0.1.285.iso"
  4. Загрузите установщик CloudBase-Init и поместите его в iso-образ:

    bash
    curl -L -o CloudBaseInit.msi "https://github.com/cloudbase/cloudbase-init/releases/download/1.1.6/CloudbaseInitSetup_1_1_6_x64.msi" && \
    mkisofs -o /iso/CloudBaseInit.iso CloudBaseInit.msi
  5. Скопируйте UEFI VARS-файл в домашнюю директорию:

    bash
    cp /usr/share/OVMF/OVMF_VARS_4M.fd ~
  6. Создайте диск для ВМ:

    bash
    qemu-img create -f qcow2 output/image.qcow2 25600M
    Примечание

    Минимальный рекомендуемый размер диска:

    • для Windows Server 2022 — 16384M;
    • для Windows Server 2025 — 25600M.
  7. Запустите ВМ:

    bash
    qemu-system-x86_64 \
    -enable-kvm \
    -machine q35 \
    -cpu host \
    -m 4096 \
    -smp 4 \
    -monitor stdio \
    -drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
    -drive if=pflash,format=raw,file=iso/OVMF_VARS_4M.fd \
    -drive file=iso/windows.iso,media=cdrom,index=0 \
    -drive file=iso/virtio-win.iso,media=cdrom,index=1 \
    -drive file=CloudBaseInit.iso,media=cdrom,index=2 \
    -boot order=d,menu=on \
    -blockdev driver=qcow2,node-name=vd.root,file.driver=file,file.filename=output/image.qcow2 \
    -device virtio-scsi-pci,id=scsi0 \
    -device scsi-hd,drive=vd.root,bus=scsi0.0 \
    -device virtio-serial \
    -device virtserialport,chardev=vdagent,name=com.redhat.spice.0 \
    -chardev spicevmc,id=vdagent,debug=0,name=vdagent \
    -spice port=3001,disable-ticketing
  8. Подключитесь к ВМ:

    bash
    remote-viewer spice://127.0.0.1:3001

    Дождитесь приглашения терминала. Введите exit. В UEFI выберите опцию Continue. Несколько раз нажмите Enter.

  1. Выберите язык, региональные настройки и нажмите Next.

  2. Нажмите Install Now.

  3. Выберите редакцию Windows Server Standard (Desktop Experience).

  4. Примите лицензионное соглашение.

  5. В списке вариантов установки ОС выберите Custom: Install Windows only (advanced).

  6. На экране выбора диска нажмите Load Driver, в новом окне укажите путь к драйверу хранилища на подключенном образе Virtio: E:\vioscsi\2k<номер версии ОС — 22 или 25>\amd64. Установите предложенный драйвер.

  7. Выберите появившийся диск и нажмите Next.

  8. Дождитесь завершения установки ОС. ВМ перезагрузится автоматически.

  9. После перезагрузки задайте пароль администратора.

  10. Установите гостевые инструменты Virtio. Запустите Windows PowerShell и введите команду:

    powershell
    Start-Process -FilePath "E:\virtio-win-guest-tools.exe" -Wait

    После установки гостевых инструментов станет доступен общий буфер обмена между гостевой и хост-системами.

4. Подготовьте образ для использования в MWS Cloud Platform

Заголовок раздела «4. Подготовьте образ для использования в MWS Cloud Platform»

Выполните в PowerShell следующие команды:

  1. Отключите энергосбережение:

    powershell
    powercfg -setactive "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c"
    powercfg -change -monitor-timeout-ac 0
    powercfg -change -standby-timeout-ac 0
    powercfg -change -hibernate-timeout-ac 0
  2. Настройте параметры реестра:

    powershell
    # Использовать UTC как аппаратное время
    Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\TimeZoneInformation" `
    -Name "RealTimeIsUniversal" -Value 1 -Type DWord -Force
    # Разрешить выключение без активных пользователей
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" `
    -Name "ShutdownWithoutLogon" -Value 1
    # Сократить таймаут предупреждения при выключении до 1 секунды
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" `
    -Name "ShutdownWarningDialogTimeout" -Value 1
  3. Отключите дефрагментацию:

    powershell
    Get-ScheduledTask -TaskName "ScheduledDefrag" | Disable-ScheduledTask
  4. Разрешите трафик по протоколу ICMP (ping):

    powershell
    Get-NetFirewallRule -Name "vm-monitoring-icmpv4" | Enable-NetFirewallRule
  5. Настройте подключение по протоколу RDP:

    powershell
    # Включить RDP
    Set-ItemProperty -Path "HKLM:\System\CurrentControlSet\Control\Terminal Server" `
    -Name "fDenyTSConnections" -Value 0
    # Отключить NLA (Network Level Authentication), чтобы RDP показывал обычный экран входа и не отбрасывал подключение на этапе предварительной проверки учетных данных
    Get-CimInstance -ClassName Win32_TSGeneralSetting `
    -Namespace root\cimv2\terminalservices |
    Invoke-CimMethod -MethodName SetUserAuthenticationRequired `
    -Arguments @{ UserAuthenticationRequired = 0 }
    # Открыть порт в firewall
    Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
  6. Удалите recovery-раздел и расширьте диск C для подготовки дискового пространства:

    powershell
    reagentc /disable
    $p = Get-Partition -DiskNumber 0 |
    Where-Object { $_.Type -eq 'Recovery' } |
    Sort-Object -Property PartitionNumber -Descending |
    Select-Object -First 1
    if ($p) {
    Remove-Partition -DiskNumber 0 -PartitionNumber $p.PartitionNumber -Confirm:$false -ErrorAction Stop
    }
    $size = Get-PartitionSupportedSize -DriveLetter C
    Resize-Partition -DriveLetter C -Size $size.SizeMax
  7. Проверьте, что QEMU Guest Agent установлен на ВМ:

    powershell
    Get-Service 'QEMU-GA'

    Если сервис не найден, установите и запустите его:

    powershell
    $msi = "E:\guest-agent\qemu-ga-x86_64.msi"
    $log = "C:\Windows\Temp\qemu-ga-install.log"
    $p = Start-Process -FilePath 'msiexec.exe' `
    -ArgumentList "/i `"$msi`" /qn /norestart /L*v `"$log`"" `
    -Wait -PassThru
    Write-Host "Exit code: $($p.ExitCode)"
    Get-Content $log -Tail 20
    for ($i = 0; $i -lt 12; $i++) {
    $svc = Get-Service -Name 'QEMU-GA' -ErrorAction SilentlyContinue
    if ($svc) { break }
    Start-Sleep 5
    }
    Set-Service -Name 'QEMU-GA' -StartupType Automatic
    Start-Service 'QEMU-GA'
  8. Выполните скрипт для установки OpenSSH:

    powershell
    $ErrorActionPreference = 'Stop'
    $cap = Get-WindowsCapability -Online |
    Where-Object { $_.Name -like 'OpenSSH.Server*' } |
    Select-Object -First 1
    Write-Host "Capability: $($cap.Name), State: $($cap.State)"
    if ($cap.State -ne 'Installed') {
    Add-WindowsCapability -Online -Name $cap.Name
    }
    for ($i = 0; $i -lt 12; $i++) {
    $svc = Get-Service -Name sshd -ErrorAction SilentlyContinue
    if ($svc) { break }
    Start-Sleep 5
    }
    Set-Service -Name sshd -StartupType Automatic
    Start-Service sshd
    $rule = Get-NetFirewallRule -DisplayName 'OpenSSH Server (sshd)' -ErrorAction SilentlyContinue
    if (-not $rule) {
    New-NetFirewallRule -DisplayName 'OpenSSH Server (sshd)' `
    -Direction Inbound -Action Allow -Protocol TCP -LocalPort 22 | Out-Null
    }
    $cfg = 'C:\ProgramData\ssh\sshd_config'
    $content = Get-Content $cfg -Raw
    $content = [regex]::Replace($content, '(?m)^\s*#?\s*PasswordAuthentication\s+.*$', 'PasswordAuthentication yes')
    if ($content -notmatch '(?m)^PasswordAuthentication\s+yes$') {
    $content = $content.TrimEnd() + "`r`nPasswordAuthentication yes`r`n"
    }
    $content = [regex]::Replace($content, '(?m)^\s*#?\s*PubkeyAuthentication\s+.*$', 'PubkeyAuthentication yes')
    if ($content -notmatch '(?m)^PubkeyAuthentication\s+yes$') {
    $content = $content.TrimEnd() + "`r`nPubkeyAuthentication yes`r`n"
    }
    Set-Content -Path $cfg -Value $content -Encoding ascii
    Restart-Service sshd
    Start-Sleep 5
    for ($i = 0; $i -lt 12; $i++) {
    $l = Get-NetTCPConnection -LocalPort 22 -State Listen -ErrorAction SilentlyContinue
    if ($l) { break }
    Start-Sleep 5
    }
    Get-NetTCPConnection -LocalPort 22 -State Listen
  9. Установите CloudBase-Init:

    powershell
    $msi = 'F:\CLOUDBAS.msi'
    Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /qn /norestart" -Wait
  10. Создайте или отредактируйте файл C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init.conf:

    ini
    [DEFAULT]
    username=Administrator
    groups=Administrators
    inject_user_password=true
    config_drive_raw_hhd=true
    config_drive_cdrom=true
    config_drive_vfat=true
    bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
    mtools_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
    verbose=false
    debug=false
    logdir=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\log\
    logfile=cloudbase-init-unattend.log
    default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN,requests=WARN
    logging_serial_port_settings=COM2,115200,N,8
    mtu_use_dhcp_config=true
    ntp_use_dhcp_config=true
    local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\
    run_scripts_once=true
    metadata_services=cloudbaseinit.metadata.services.nocloudservice.NoCloudConfigDriveService
    plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin,cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin,cloudbaseinit.plugins.windows.extendvolumes.ExtendVolumesPlugin,cloudbaseinit.plugins.common.userdata.UserDataPlugin,cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin
    allow_reboot=false
    stop_service_on_exit=false
    check_latest_version=false
    first_logon_behaviour=always
  11. Продублируйте файл для фазы подготовки ВМ:

    powershell
    $confDir = 'C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf'
    Copy-Item -Force "$confDir\cloudbase-init.conf" "$confDir\cloudbase-init-unattend.conf"
  12. Создайте скрипт для принудительной смены пароля при первом входе в ОС по пути C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\10-force-password-change-from-nocloud.ps1:

    powershell
    $ErrorActionPreference = "Stop"
    function Write-Log {
    param([string]$Message)
    Write-Host "[LocalScripts][ForcePasswordChange] $Message"
    }
    function Get-SeedDrive {
    $volumes = Get-CimInstance Win32_LogicalDisk | Where-Object {
    $_.DriveType -in @(2, 3, 5)
    }
    foreach ($v in $volumes) {
    try {
    $root = "$($v.DeviceID)\"
    $userDataPath = Join-Path $root "user-data"
    if ((Test-Path $userDataPath) -or ($v.VolumeName -match '^(?i:cidata|config-2)$')) {
    if (Test-Path $userDataPath) {
    Write-Log "Found seed drive: $root (label='$($v.VolumeName)')"
    return $root
    }
    }
    } catch {
    continue
    }
    }
    throw "No NoCloud seed drive with user-data found"
    }
    function Get-UsernameFromUserData {
    param(
    [Parameter(Mandatory = $true)]
    [string]$Path
    )
    $raw = Get-Content -Path $Path -Raw -Encoding UTF8
    if ([string]::IsNullOrWhiteSpace($raw)) {
    throw "user-data is empty"
    }
    $match = [regex]::Match(
    $raw,
    '(?m)^\s*-\s*name\s*:\s*["'']?([^"''\r\n#]+)["'']?\s*$'
    )
    if (-not $match.Success) {
    throw "Could not find username in user-data"
    }
    $username = $match.Groups[1].Value.Trim()
    if ([string]::IsNullOrWhiteSpace($username)) {
    throw "Parsed username is empty"
    }
    return $username
    }
    function Ensure-LocalUserExists {
    param(
    [Parameter(Mandatory = $true)]
    [string]$Username
    )
    try {
    $null = Get-LocalUser -Name $Username -ErrorAction Stop
    }
    catch {
    throw "Local user '$Username' not found"
    }
    }
    function Set-RequirePasswordChange {
    param(
    [Parameter(Mandatory = $true)]
    [string]$Username
    )
    Write-Log "Configuring password policy for '$Username'"
    $user = [ADSI]"WinNT://./$Username,user"
    $ADS_UF_DONT_EXPIRE_PASSWD = 0x10000
    $flags = $user.UserFlags.Value
    if ($flags -band $ADS_UF_DONT_EXPIRE_PASSWD) {
    Write-Log "Removing 'Password never expires' flag"
    $user.UserFlags = $flags -bxor $ADS_UF_DONT_EXPIRE_PASSWD
    $user.SetInfo()
    }
    Write-Log "Requiring password change at next logon"
    & net.exe user $Username /logonpasswordchg:yes
    if ($LASTEXITCODE -ne 0) {
    throw "Failed to set logonpasswordchg for $Username"
    }
    }
    try {
    $seedRoot = Get-SeedDrive
    $userDataPath = Join-Path $seedRoot "user-data"
    Write-Log "Reading user-data: $userDataPath"
    $username = Get-UsernameFromUserData -Path $userDataPath
    Write-Log "Parsed username: $username"
    Ensure-LocalUserExists -Username $username
    Set-RequirePasswordChange -Username $username
    Write-Log "Done"
    exit 0
    }
    catch {
    Write-Error $_.Exception.Message
    exit 1
    }
  13. Создайте файл конфигурации sysprep C:\sysprep\unattend.xml и скопируйте в него следующее содержимое:

    xml
    <?xml version="1.0" encoding="utf-8"?>
    <unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="generalize">
    <component name="Microsoft-Windows-PnpSysprep"
    processorArchitecture="amd64"
    publicKeyToken="31bf3856ad364e35"
    language="neutral"
    versionScope="nonSxS"
    xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <PersistAllDeviceInstalls>true</PersistAllDeviceInstalls>
    </component>
    </settings>
    <settings pass="oobeSystem">
    <component name="Microsoft-Windows-Shell-Setup"
    processorArchitecture="amd64"
    publicKeyToken="31bf3856ad364e35"
    language="neutral"
    versionScope="nonSxS"
    xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
    <OOBE>
    <HideEULAPage>true</HideEULAPage>
    <HideLocalAccountScreen>true</HideLocalAccountScreen>
    <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
    <HideOnlineAccountScreens>true</HideOnlineAccountScreens>
    <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
    <NetworkLocation>Work</NetworkLocation>
    <ProtectYourPC>1</ProtectYourPC>
    <SkipMachineOOBE>true</SkipMachineOOBE>
    <SkipUserOOBE>true</SkipUserOOBE>
    </OOBE>
    <TimeZone>UTC</TimeZone>
    </component>
    </settings>
    <settings pass="specialize">
    <component name="Microsoft-Windows-Deployment"
    processorArchitecture="amd64"
    publicKeyToken="31bf3856ad364e35"
    language="neutral"
    versionScope="nonSxS"
    xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <RunSynchronous>
    <RunSynchronousCommand wcm:action="add">
    <Order>1</Order>
    <Path>cmd.exe /c ""C:\Program Files\Cloudbase Solutions\Cloudbase-Init\Python\Scripts\cloudbase-init.exe" --config-file "C:\Program Files\Cloudbase Solutions\Cloudbase-Init\conf\cloudbase-init-unattend.conf" && exit 1 || exit 2"</Path>
    <Description>Run Cloudbase-Init to set the hostname</Description>
    </RunSynchronousCommand>
    </RunSynchronous>
    </component>
    </settings>
    </unattend>

    Sysprep используется для подготовки ОС к работе в облаке: сбрасывает SID, имя компьютера и историю устройств, а также выключает ВМ.

  14. Запустите sysprep:

    powershell
    & $env:SystemRoot\System32\Sysprep\Sysprep.exe `
    /oobe /generalize /quiet /shutdown `
    /unattend:"C:\sysprep\unattend.xml"

По завершении ВМ выключится автоматически.

Для загрузки образа в облако установите утилиту AWS CLI:

  1. Создайте сервисный аккаунт с правами storage.bucket.editor. Этот сервисный аккаунт будет использоваться для работы AWS CLI.

  2. Для сервисного аккаунта создайте HMAC-ключ. Сохраните обе части HMAC-ключа — Access key и Secret key. Они понадобятся далее.

  3. Скачайте и установите утилиту AWS CLI:

    bash
    curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv.zip" && \
    unzip awscliv.zip && \
    sudo ./aws/install
  4. Настройте AWS CLI:

    bash
    aws configure

    Укажите следующие параметры:

    • AWS Access Key ID — значение Access key из HMAC-ключа (пример — _Ukpnpw3RiCx3iuPhn-t);
    • AWS Secret Access Key — значение Secret key из HMAC-ключа (пример — 3LIK2LbwYqER7oGr3cX4WSC31YN83yZnDgcXyjewX);
    • Default region nameru-central1;
    • Default output formattext.
  1. Создайте бакет Object Storage, в который будете загружать образ. В этом руководстве для примера используется имя бакета test-bucket:

    bash
    aws s3 mb s3://test-bucket --endpoint-url https://storage.mwsapis.ru

    Результат успешного создания бакета:

    bash
    make_bucket: test-bucket
  2. Загрузите образ в бакет:

    bash
    aws s3 cp image.qcow2 s3://test-bucket/win-server.qcow2 --endpoint-url https://storage.mwsapis.ru

    Дождитесь окончания загрузки.

7. Добавьте образ в список пользовательских образов Compute

Заголовок раздела «7. Добавьте образ в список пользовательских образов Compute»

Образ win-server.qcow2 сейчас загружен в бакет в виде файла. Чтобы использовать образ в качестве источника при создании новых ВМ, добавьте его в список пользовательских образов Compute.

Добавить образ можно двумя способами:

Важно

Настройка политики доступа через веб-консоль откроет полный доступ к файлу образа из интернета. Не используйте этот способ для добавления образов, содержащих чувствительные данные.

  • Утилиты CLI
  • Веб-консоль
  1. Создайте сервисный аккаунт с правами storage.image.editor. Этот сервисный аккаунт будет использоваться для работы MWS CLI.

  2. Установите и настройте утилиту MWS CLI:

    1. Инициализируйте профиль. При инициализации используйте созданный ранее сервисный аккаунт с ролью storage.image.editor.

  3. Сгенерируйте подписанную ссылку для доступа к образу:

    bash
    aws s3 presign s3://test-bucket/win-server.qcow2 --expires-in 60480 --endpoint-url https://storage.mwsapis.ru

    Пример успешного выполнения запроса:

    bash
    https://storage.mwsapis.ru/test-bucket/win-server.qcow2?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=rNw605bp9MIQXFEe-QJF%2F20250812%2Fru-central1%2Fs3%2Faws4_request&X-Amz-Date=20250812T033321Z&X-Amz-Expires=60480&X-Amz-SignedHeaders=host&X-Amz-Signature=3a30dd9402a69b6573cd8f72571faf683eb6fe660b8f4de256dfdfe91e8297b3
  4. Добавьте образ в список пользовательских образов Compute::

    bash
    mws compute image create win-server \
    --project <имя проекта> \
    --os-type WINDOWS \
    --family <(опционально) семейство> \
    --external-url <подписанная ссылка для доступа к образу>

Пользовательский образ станет доступен для выбора при создании ВМ.