Эта статья НЕ про майнкрафт. Эта статья про Azure и про современные подходы к деливери ПО.
Если вы просто хотите развернуть себе сервер майнкрафт — спросите гугл «minecraft server hosting» — будет и проще и дешевле.
А вот если вы хотите посмотреть на реальный кейс использования подходов Infrastructure as Code, Configuration as Code применительно к неадаптированному к Azure ПО на примере майнкрафта — то добро пожаловать
Azure Stack огромен и хочется потыкать в разные его части, поэтому эта статья будет всяко не одна.
План статей пока видится такой:
Но конечно процесс появления новых статей может быть остановлен отсутствием вашего интереса )
Итак.
Задача не синтетическая. Сервер попросили развернуть дети и он реально будет использоваться. Меньше всего я хочу его постоянно чинить и поднимать. Ещё меньше я хочу терять данные при обновлениях. Поэтому требования к нему вполне себе prod-like )
Все пункты буду пояснять по ходу дела
В качестве сервера я взял официальный сервер minecraft.net/en-us/download/server. Беглое изучение выявило, что:
Применим самую базовую схему:
одна Resource Group c:

Данные будем хранить отдельно от виртуальной машины на отдельном managed disk и линковать в папочку World рядом с jar'кой с помощью Symlink (я писал выше, что расположение папочки world не настраивается и должно быть всегда рядом с jar).
Это даст возможность запускать сервер как с прилинкованным диском, так и без (для тестов) — в этом случае папка world будет создана с пустой картой.
Сервер будем конфигурировать на основании:
Я там выше обещал следования некоторым требованиям конфигурационного управления — вот первое:
Именно поэтому мы и дистрибутив и конфигурацию будем предварительно собирать из артефактов в интернетах и перепубликовывать в Blob Storage.
Таким образом, алгоритм процедуры развертывания следующий:
реализация тривиальная, просто вызовы cli
Скачиваем сервер и карту, jre берём с текущего компьютера и всё пакуем в zip
структура дистрибутива:
DSC конфигурация представляет собой один ps1 файл (о структуре его — позже), который будет запускаться на сервере и его конфигурировать.
Но скрипты в ps1 файле имеют депенды на внешние модули, которые не будут установлены на сервер автоматически.
Поэтому конфигурацию надо передать на сервер не просто как ps1 файл, а как архив с:
Зависимые модули можно просто скачать curl'ом как nuget пакет с репозитория powershell gallery и разархивировать в нужное место.
Также можно использовать и команду powershell Save-Module — которая включает эти два шага
Обратите внимание, что версии зависимых модулей и в ps1 файле и в скриптах формирования архива с конфигурацией указаны и совпадают.
И тут мы опять следуем требованиям очередного правила конфигурационного управления:
реализация тривиальная, просто вызовы cli:
Вот тут, наверное самое интересное место. Конфигурирование сервера происходит на основании DSC конфигурации, загруженной ранее с помощью DSC расширения для vm.
DSC extension кормится конфигом в котором указан URL до архива с конфигурацией и значения параметров
Ну это некоторая надстройка над powershell, которая позволяет декларативно описывать требуемое состояние компьютера.
DSC конфигурация анализируется на целевой машине, строится диф относительно текущей конфигурации этой машины, а затем происходит приведение конфигурации к нужной. Ближайший аналог powershell DSC в «opensource» мире — Ansible.
Структурно конфигурация состоит из набора шагов, которые выполняются в определенном порядке (с учётом зависимостей). Пробежимся по всем:
Декларирует, что на компьютере должен лежать zip с дистрибутивом сервера по пути DestinationPath, если его там не лежит — он будет скачен по адресу Uri:
Декларирует, что на компьютере в папке Destination должен лежить разархивированный архив с дистрибутивом сервера если его там не лежит (или если состав файлов отличается от того что в архиве) — они будут добавлены:
Декларирует, что по пути DestinationPath должны лежать файлы с конфигом сервера и с лицензией. Если их нет — они создаются с содержимым Contents:
Тут мы декларируем о том, что managed disk с данными должен быть инициализирован в OS и ему должна быть присвоена буква F:
Декларируем о том, что на диске F должна присутствовать директори world (с миром). Если её там нет — мы считаем что происходит деплой впервые и нам надо эту папку создать, взяв за основу карту из initial_world дистрибутива:
Это кастомный шаг — проверяем, что у нас в папке с сервером есть папка World. Если её нету — линкуем её с диска F. Script работает следующим образом — если TestScript вернул false — вызывается SetScript. Иначе не вызывается:
Ещё один кастомный шаг. Проверяем, что запущен java процесс с сервером ). Если не запущен — запускаем:
И последние шаги — открываем 25565 порт.
Статья получилась и так длинная, надо закругляться. Остались за бортом вопросы дебага этого DSC (очень интересные сами по себе) — освещу их в следующей.
Всем спасибо за внимание.
Если вы просто хотите развернуть себе сервер майнкрафт — спросите гугл «minecraft server hosting» — будет и проще и дешевле.
А вот если вы хотите посмотреть на реальный кейс использования подходов Infrastructure as Code, Configuration as Code применительно к неадаптированному к Azure ПО на примере майнкрафта — то добро пожаловать
Roadmap
Azure Stack огромен и хочется потыкать в разные его части, поэтому эта статья будет всяко не одна.
План статей пока видится такой:
- Автоматизированный деплой и настройка сервера с применением Azure CLI 2 и Powershell DSC
- Поддержка конфигурации сервера в консистентном состоянии с применением Azure Automation
- Автоматизированное обновление сервера на новую версию
- Получение логов майнкрафта, анализ и отображение с применением Azure Stream Analytics и Power BI
- Организация active/passive кластера с управлением через Azure Automation
- ...
Но конечно процесс появления новых статей может быть остановлен отсутствием вашего интереса )
Disclaimer
- Я не it инженер, не devops инженер, не игрок в майнкрафт. Поэтому если вы шарите и вам кажется, что я всё делаю не правильно — то скорее всего так оно и есть — напишите в коментах как надо.
- Все примеры к статье 100% рабочие, но поставляются «как есть». Если у вас что-то не работает — почините это самостоятельно или забейте.
Итак.
Автоматизированный деплой и настройка сервера с применением Azure CLI 2 и Powershell DSC
О задаче
Задача не синтетическая. Сервер попросили развернуть дети и он реально будет использоваться. Меньше всего я хочу его постоянно чинить и поднимать. Ещё меньше я хочу терять данные при обновлениях. Поэтому требования к нему вполне себе prod-like )
Общий подход
- 100% автоматизация.
- Относимся к скриптам как к коду
- Соблюдаем базовые требования конфигурационного управления к деливери процессу на prod окружения
Все пункты буду пояснять по ходу дела
О ПО
В качестве сервера я взял официальный сервер minecraft.net/en-us/download/server. Беглое изучение выявило, что:
- Сервер представляет из себя jar файл и требует для запуска java
- Для запуска ему требуется рядом с собой файл eula.txt с определённым содержимым
- Сервер конфигурируется с помощью файла server.properties который тоже должен лежать рядом
- Сервер слушает 25565 порт по протоколу TCP
- Данные (карту) сервер хранит в папочке World рядом с собой (и это будет слегка проблемой)
- Сервер умеет данные апгрейдить, если они созданы предыдущей версией (и это сильно упростит жизнь при апгрейде)
- Логи хранятся в папочке Logs рядом
Инфраструктура
Применим самую базовую схему:
одна Resource Group c:
- Network Security Group (будут открыты порты 25565 (minecraft) и 3389 (rdp)
- VNET c 1 subnet
- NIC и статический public IP c DNS именем
- 1 VM под Windows Server 2016 core + 2 Managed Disk (OS + Data)
- Storage account и 1 container для Blob storage

Данные будем хранить отдельно от виртуальной машины на отдельном managed disk и линковать в папочку World рядом с jar'кой с помощью Symlink (я писал выше, что расположение папочки world не настраивается и должно быть всегда рядом с jar).
Это даст возможность запускать сервер как с прилинкованным диском, так и без (для тестов) — в этом случае папка world будет создана с пустой картой.
Сервер будем конфигурировать на основании:
- Дистрибутива (jar с сервером + jre + начальные данные), который будет предварительно загружаться в Blob storage
- Конфигурации (Powershell DSC), которая также будет предварительно загружаться в Blob storage
Я там выше обещал следования некоторым требованиям конфигурационного управления — вот первое:
все артефакты, которые требуются для разворачивания приложения, должны быть опубликованы в подконтрольный сторадж с высокой доступностью.
Именно поэтому мы и дистрибутив и конфигурацию будем предварительно собирать из артефактов в интернетах и перепубликовывать в Blob Storage.
Таким образом, алгоритм процедуры развертывания следующий:
- Создать новую Resource Group, Storage Account и Storage Container
- Сформировать дистрибутив (jar с сервером + jre + начальные данные) и загрузить его в Blob Storage
- Создать всю остальную инфраструктуру в Resource Group
- Сформировать DSC конфигурацию и загрузить её в Blob Storage
- Сконфигурировать сервер на основании конфигурации и дистрибутива
Исходники
- Храним на github
- Соблюдаем какую-нибудь внятную стратегию бранчевания и релизного цикла (ну например gitflow и релиз каждую статью :))
- Используем azure cli 2 и bash там, где это возможно (к слову, в этой статье удалось совсем избежать использования powershell api. А вот в следующей это уже не получится)
Реализация шагов процедуры развертывания
1. Создать новую Resource Group, Storage Account и Storage Container
реализация тривиальная, просто вызовы cli
initial_preparation.sh
az configure --defaults location=$LOCATION group=$GROUP echo "create new group" az group create -n $GROUP echo "create storage account" az storage account create -n $STORAGE_ACCOUNT --sku Standard_LRS STORAGE_CS=$(az storage account show-connection-string -n $STORAGE_ACCOUNT) export AZURE_STORAGE_CONNECTION_STRING="$STORAGE_CS" echo "create storage container" az storage container create -n $STORAGE_CONTAINER --public-access blob
2. Сформировать дистрибутив (jar с сервером + jre + начальные данные) и загрузить его в Blob Storage
Скачиваем сервер и карту, jre берём с текущего компьютера и всё пакуем в zip
структура дистрибутива:
- .jar
- jre
- initial_world — папка с начальной картой
prepare_distr.sh
mkdir $DISTR_DIR cd $DISTR_DIR echo "download minecraft server from official site" curl -Os https://s3.amazonaws.com/Minecraft.Download/versions/1.12.2/minecraft_server.$MVERSION.jar echo "copy jre from this machine" cp -r "$JRE" ./jre echo "create ititial world folder" mkdir initial_world cd initial_world echo "download initial map" curl -Os https://dl01.mcworldmap.com/user/1821/world2.zip unzip -q world2.zip cp -r StarWars/* . rm -r -f StarWars rm world2.zip cd ../ echo "create archive (zip utility -> https://ranxing.wordpress.com/2016/12/13/add-zip-into-git-bash-on-windows/)" cd ../ zip -r -q $DISTR_ZIP $DISTR_DIR rm -r -f $DISTR_DIR
3. Сформировать DSC конфигурацию и загрузить её в Blob Storage
DSC конфигурация представляет собой один ps1 файл (о структуре его — позже), который будет запускаться на сервере и его конфигурировать.
Но скрипты в ps1 файле имеют депенды на внешние модули, которые не будут установлены на сервер автоматически.
Поэтому конфигурацию надо передать на сервер не просто как ps1 файл, а как архив с:
- ps1 файлом
- Всеми зависимыми модулями, сложенными в одноименных папочках рядом с ps1 файлом
Зависимые модули можно просто скачать curl'ом как nuget пакет с репозитория powershell gallery и разархивировать в нужное место.
Также можно использовать и команду powershell Save-Module — которая включает эти два шага
Обратите внимание, что версии зависимых модулей и в ps1 файле и в скриптах формирования архива с конфигурацией указаны и совпадают.
И тут мы опять следуем требованиям очередного правила конфигурационного управления:
Все зависимости должны быть точно определены и ресолвиться всегда однозначно
prepare_config.sh
echo "prepare server configuration" curl -s -L -o configuration/xPSDesiredStateConfiguration.zip "https://www.powershellgallery.com/api/v2/package/xPSDesiredStateConfiguration/7.0.0.0" curl -s -L -o configuration/xNetworking.zip "https://www.powershellgallery.com/api/v2/package/xNetworking/5.1.0.0" curl -s -L -o configuration/xStorage.zip "https://www.powershellgallery.com/api/v2/package/xStorage/3.2.0.0" cd configuration unzip -q xPSDesiredStateConfiguration.zip -d xPSDesiredStateConfiguration rm -r xPSDesiredStateConfiguration.zip unzip -q xNetworking.zip -d xNetworking rm -r xNetworking.zip unzip -q xStorage.zip -d xStorage rm -r xStorage.zip zip -r -q ../$CONFIG_ZIP . * rm -r -f xPSDesiredStateConfiguration rm -r -f xNetworking rm -r -f xStorage cd ../
4. Создать всю остальную инфраструктуру в Resource Group
реализация тривиальная, просто вызовы cli:
iaas_preparation.sh
echo "create network security group and rules" az network nsg create -n $NSG az network nsg rule create --nsg-name $NSG -n AllowMinecraft --destination-port-ranges 25556 --protocol Tcp --priority 100 az network nsg rule create --nsg-name $NSG -n AllowRDP --destination-port-ranges 3389 --protocol Tcp --priority 110 echo "create vnet, nic and pip" NIC_NAME=minesrvnic PIP_NAME=minepip SUBNET_NAME=servers az network vnet create -n $VNET --subnet-name $SUBNET_NAME az network public-ip create -n $PIP_NAME --dns-name $DNS --allocation-method Static az network nic create --vnet-name $VNET --subnet $SUBNET_NAME --public-ip-address $PIP_NAME -n $NIC_NAME echo "create data disk" DISK_NAME=minedata az disk create -n $DISK_NAME --size-gb 10 --sku Standard_LRS echo "create server vm" az vm create -n $VM_NAME --size $VM_SIZE --image $VM_IMAGE \ --nics $NIC_NAME \ --admin-username $VM_ADMIN_LOGIN --admin-password $VM_ADMIN_PASSWORD \ --os-disk-name ${VM_NAME}disk --attach-data-disk $DISK_NAME
5. Сконфигурировать сервер на основании конфигурации и дистрибутива
Вот тут, наверное самое интересное место. Конфигурирование сервера происходит на основании DSC конфигурации, загруженной ранее с помощью DSC расширения для vm.
DSC extension кормится конфигом в котором указан URL до архива с конфигурацией и значения параметров
server_configuration.sh
echo "prepare dsc extension settings" cat MinecraftServerDSCSettings.json | envsubst > ThisMinecraftServerDSCSettings.json echo "configure vm" az vm extension set \ --name DSC \ --publisher Microsoft.Powershell \ --version 2.7 \ --vm-name $VM_NAME \ --resource-group $GROUP \ --settings ThisMinecraftServerDSCSettings.json rm -f ThisMinecraftServerDSCSettings.json
О Powershell DSC
Ну это некоторая надстройка над powershell, которая позволяет декларативно описывать требуемое состояние компьютера.
DSC конфигурация анализируется на целевой машине, строится диф относительно текущей конфигурации этой машины, а затем происходит приведение конфигурации к нужной. Ближайший аналог powershell DSC в «opensource» мире — Ansible.
Реализация DSC конфигурации MinecraftServer
Структурно конфигурация состоит из набора шагов, которые выполняются в определенном порядке (с учётом зависимостей). Пробежимся по всем:
xRemoteFile DistrCopy { Uri = "https://$accountName.blob.core.windows.net/$containerName/mineserver.$minecraftVersion.zip" DestinationPath = "$mineHome.zip" MatchSource = $true }
Декларирует, что на компьютере должен лежать zip с дистрибутивом сервера по пути DestinationPath, если его там не лежит — он будет скачен по адресу Uri:
Archive UnzipServer { Ensure = "Present" Path = "$mineHome.zip" Destination = $mineHomeRoot DependsOn = "[xRemoteFile]DistrCopy" Validate = $true Force = $true }
Декларирует, что на компьютере в папке Destination должен лежить разархивированный архив с дистрибутивом сервера если его там не лежит (или если состав файлов отличается от того что в архиве) — они будут добавлены:
File CheckProperties { DestinationPath = "$mineHome\server.properties" Type = "File" Ensure = "Present" Force = $true Contents = "....." } File CheckEULA { DestinationPath = "$mineHome\eula.txt" Type = "File" Ensure = "Present" Force = $true Contents = "..." DependsOn = "[File]CheckProperties" }
Декларирует, что по пути DestinationPath должны лежать файлы с конфигом сервера и с лицензией. Если их нет — они создаются с содержимым Contents:
xWaitForDisk WaitWorldDisk { DiskIdType = "Number" DiskId = "2" RetryIntervalSec = 60 RetryCount = 5 DependsOn = "[File]CheckEULA" } xDisk PrepareWorldDisk { DependsOn = "[xWaitForDisk]WaitWorldDisk" DiskIdType = "Number" DiskId = "2" DriveLetter = "F" AllowDestructive = $false } xWaitForVolume WaitForF { DriveLetter = 'F' RetryIntervalSec = 5 RetryCount = 10 DependsOn = "[xDisk]PrepareWorldDisk" }
Тут мы декларируем о том, что managed disk с данными должен быть инициализирован в OS и ему должна быть присвоена буква F:
File WorldDirectoryExists { Ensure = "Present" Type = "Directory" Recurse = $true DestinationPath = "F:\world" SourcePath = "$mineHome\initial_world" MatchSource = $false DependsOn = "[xWaitForVolume]WaitForF" }
Декларируем о том, что на диске F должна присутствовать директори world (с миром). Если её там нет — мы считаем что происходит деплой впервые и нам надо эту папку создать, взяв за основу карту из initial_world дистрибутива:
Script LinkWorldDirectory { DependsOn="[File]WorldDirectoryExists" GetScript= { @{ Result = (Test-Path "$using:mineHome\World") } } SetScript= { New-Item -ItemType SymbolicLink -Path "$using:mineHome\World" -Confirm -Force -Value "F:\world" } TestScript= { return (Test-Path "$using:mineHome\World") } }
Это кастомный шаг — проверяем, что у нас в папке с сервером есть папка World. Если её нету — линкуем её с диска F. Script работает следующим образом — если TestScript вернул false — вызывается SetScript. Иначе не вызывается:
Script EnsureServerStart { DependsOn="[Script]LinkWorldDirectory" GetScript= { @{ Result = (Get-Process -Name java -ErrorAction SilentlyContinue) } } SetScript= { Start-Process -FilePath "$using:mineHome\jre\bin\java" -WorkingDirectory "$using:mineHome" -ArgumentList "-Xms512M -Xmx512M -jar `"$using:mineHome\minecraft_server.$using:minecraftVersion.jar`" nogui" } TestScript= { return (Get-Process -Name java -ErrorAction SilentlyContinue) -ne $null } }
Ещё один кастомный шаг. Проверяем, что запущен java процесс с сервером ). Если не запущен — запускаем:
xFirewall FirewallIn { Name = "Minecraft-in" Action = "Allow" LocalPort = ('25565') Protocol = 'TCP' Direction = 'Inbound' } xFirewall FirewallOut { Name = "Minecraft-out" Action = "Allow" LocalPort = ('25565') Protocol = 'TCP' Direction = 'Outbound' }
И последние шаги — открываем 25565 порт.
Эпилог
Статья получилась и так длинная, надо закругляться. Остались за бортом вопросы дебага этого DSC (очень интересные сами по себе) — освещу их в следующей.
Всем спасибо за внимание.
Как всё это запустить из Windows
Нам нужен будет
Запуск процедуры ролаута
Результат:

Запуск процедуры ролаута
git clone https://github.com/AndreyPoturaev/minecraft-in-azure cd minecraft-in-azure git checkout v1.0.0 export MINESERVER_PASSWORD=<place your password here> export MINESERVER_DNS=<place unique name here. Server url will be $MINESERVER_DNS.westeurope.cloudapp.azure.com> export MINESERVER_STORAGE_ACCOUNT=<place unique name here> az login . rollout.sh
Результат:

