Docker для Minecraft серверов: плюсы, минусы, подводные камни

Docker для Minecraft серверов: плюсы, минусы, подводные камни

Допустим, у вас три Minecraft-сервера: лобби, выживание и мини-игры. Каждый требует свою версию Java, свои плагины, свои настройки JVM. Обновляете один - ломаете другой. Знакомая история? Docker решает именно эту проблему. Каждый сервер живёт в изолированном контейнере со своим окружением, и они не мешают друг другу.

Но Docker - это не серебряная пуля. У него есть реальные ограничения, особенно для игровых серверов, где важна каждая миллисекунда. В этой статье разберём, когда Docker действительно полезен, а когда создаёт больше проблем, чем решает.

Зачем Docker для Minecraft

Основная идея Docker - изоляция. Каждый контейнер содержит всё необходимое для работы приложения: операционную систему, Java, сервер, плагины. Если контейнер упал, остальные продолжают работать. Если нужно обновить Java с 17 на 21 для одного сервера - это не затронет другие.

Воспроизводимость. Docker-образ содержит фиксированное окружение. То, что работает на вашей тестовой машине, будет работать точно так же на продакшене. Нет больше "а у меня всё работало" - если образ собрался, он запустится одинаково на любой машине с Docker.

Простота деплоя. Новый сервер - это один docker-compose up -d. Не нужно ставить Java, настраивать переменные окружения, скачивать jar-файлы вручную. Всё описано в docker-compose.yml, и любой член команды может развернуть идентичное окружение.

Изоляция ресурсов. Docker использует cgroups для ограничения CPU и памяти. Если сервер мини-игр внезапно съел всю память из-за утечки в плагине, он не утянет за собой лобби и выживание. OOM-киллер убьёт только один контейнер.

Простые бэкапы. Данные хранятся в Docker volumes или bind mounts. Бэкап - это копирование директории. Восстановление - замена директории и перезапуск контейнера. Никаких сложных процедур.

Образ itzg/minecraft-server

Не нужно писать свой Dockerfile с нуля. Образ itzg/minecraft-server - это де-факто стандарт для запуска MC в Docker. Он поддерживает Paper, Spigot, Fabric, Forge, Velocity, BungeeCord и десятки других серверных платформ.

Минимальный запуск:

docker run -d \
  --name mc-server \
  -p 25565:25565 \
  -e EULA=TRUE \
  -e TYPE=PAPER \
  -e VERSION=1.21.4 \
  -v mc-data:/data \
  itzg/minecraft-server

Контейнер скачает Paper 1.21.4, примет EULA, создаст мир и начнёт слушать порт 25565. Данные сохранятся в volume mc-data.

Полезные переменные окружения:

  • MEMORY=4G - выделить 4 ГБ под JVM (устанавливает и -Xms, и -Xmx)
  • TYPE=PAPER - тип сервера (PAPER, SPIGOT, FABRIC, FORGE, VELOCITY, BUNGEECORD)
  • VERSION=1.21.4 - версия Minecraft
  • ONLINE_MODE=FALSE - для серверов за прокси (Velocity/BungeeCord)
  • OPS=player1,player2 - список операторов
  • DIFFICULTY=hard - сложность
  • VIEW_DISTANCE=10 - дальность прорисовки
  • MAX_PLAYERS=100 - лимит игроков
  • JVM_OPTS=-XX:+UseG1GC -XX:+ParallelRefProcEnabled - дополнительные JVM-флаги

Docker Compose: один сервер

Для продакшена всегда используйте docker-compose вместо голого docker run. Конфиг в файле проще поддерживать, версионировать и передавать коллегам.

version: "3.8"

services:
  minecraft:
    image: itzg/minecraft-server:latest
    container_name: mc-survival
    restart: unless-stopped
    environment:
      EULA: "TRUE"
      TYPE: PAPER
      VERSION: "1.21.4"
      MEMORY: "6G"
      VIEW_DISTANCE: "12"
      MAX_PLAYERS: "50"
      DIFFICULTY: "hard"
      SPAWN_PROTECTION: "0"
      SNOOPER_ENABLED: "FALSE"
      JVM_OPTS: >-
        -XX:+UseG1GC
        -XX:+ParallelRefProcEnabled
        -XX:MaxGCPauseMillis=200
        -XX:+UnlockExperimentalVMOptions
        -XX:+DisableExplicitGC
        -XX:G1NewSizePercent=30
        -XX:G1MaxNewSizePercent=40
        -XX:G1HeapRegionSize=8M
        -XX:G1ReservePercent=20
        -XX:G1MixedGCCountTarget=4
        -XX:InitiatingHeapOccupancyPercent=15
        -XX:G1MixedGCLiveThresholdPercent=90
        -XX:SurvivorRatio=32
        -XX:+PerfDisableSharedMem
        -XX:MaxTenuringThreshold=1
    volumes:
      - ./server-data:/data
    ports:
      - "25565:25565"
    deploy:
      resources:
        limits:
          memory: 8G
          cpus: "4.0"
        reservations:
          memory: 6G
          cpus: "2.0"

Обратите внимание на deploy.resources. Здесь мы ставим жёсткие лимиты: контейнер не сможет использовать больше 8 ГБ RAM и 4 ядер CPU. При этом резервируем минимум 6 ГБ и 2 ядра. Лимит памяти Docker должен быть выше, чем MEMORY для JVM - Java потребляет больше, чем размер heap (метаспейс, нативная память, потоки).

Volumes: миры, плагины, конфиги

Правильная организация volumes - ключ к удобному управлению. У вас два варианта: Docker volumes и bind mounts.

Bind mounts (рекомендую для MC) - маппинг конкретной директории хоста в контейнер:

volumes:
  - ./server-data:/data

Все файлы сервера будут в ./server-data на хосте. Можно редактировать конфиги напрямую, копировать плагины через scp, делать бэкапы обычным rsync.

Docker volumes - управляемые Docker'ом хранилища:

volumes:
  - mc-data:/data

volumes:
  mc-data:

Данные хранятся в /var/lib/docker/volumes/mc-data/_data. Более изолированно, но менее удобно для прямого доступа.

Для Minecraft серверов bind mounts практичнее. Типичная структура:

./server-data/
  world/           # основной мир
  world_nether/    # незер
  world_the_end/   # энд
  plugins/         # JAR-файлы и конфиги плагинов
  server.properties
  paper-global.yml
  paper-world-defaults.yml
  ops.json
  whitelist.json

Чтобы добавить плагин, просто скопируйте JAR в ./server-data/plugins/ и перезапустите контейнер.

Сеть: host vs bridge

Это критически важный выбор для игровых серверов.

Bridge (по умолчанию). Docker создаёт виртуальную сеть. Трафик проходит через NAT. Для большинства веб-приложений это нормально. Для Minecraft - добавляет задержку. NAT обрабатывает каждый пакет, и при 100 игроках это ощутимо. Плюс: порт-форвардинг, изоляция сетей.

Host mode. Контейнер использует сетевой стек хоста напрямую. Нет NAT, нет оверхеда. Пакеты идут прямо в Java-процесс. Минус: нет сетевой изоляции, контейнер занимает порты хоста.

services:
  minecraft:
    image: itzg/minecraft-server:latest
    network_mode: host
    environment:
      EULA: "TRUE"
      TYPE: PAPER
      SERVER_PORT: "25565"

Для игровых серверов используйте host mode. Разница в latency 1-3 мс может показаться мелочью, но при 20 тиках в секунду и десятках игроков она накапливается. Bridge mode оправдан, если вам нужна изоляция сетей между контейнерами или если на одной машине несколько серверов на разных портах.

Производительность: Java в контейнерах

Java в Docker работает хорошо, но есть нюансы.

Память. JVM видит лимиты cgroups и корректно определяет доступную память (в Java 10+ это из коробки). Если вы поставили MEMORY=6G и Docker-лимит 8G, всё будет корректно. Но не ставьте Docker-лимит равным размеру heap - JVM нужна память сверх heap (200-500 МБ на метаспейс, потоки, нативные буферы).

CPU. Docker использует CPU shares и CFS quota. По умолчанию контейнеры делят CPU пропорционально. Жёсткий лимит (cpus: "4.0") может привести к троттлингу - GC-пауза не сможет использовать больше 4 ядер, даже если они свободны. Для одиночного сервера лучше не ставить жёсткий лимит CPU, а использовать cpu_shares для приоритизации.

Диск. Overlay2 (стандартная файловая система Docker) добавляет минимальный оверхед при чтении. Но при активной записи (сохранение мира, логи) разница может быть заметна. Bind mounts работают без overlay - прямой доступ к файловой системе хоста. Ещё одна причина использовать bind mounts для данных.

Сеть. Про bridge vs host уже говорили. Важно: если используете bridge, включите jumbo frames на Docker-сети не получится - MTU фиксирован. При большом количестве чанков, летящих игроку, это может увеличить количество пакетов.

Мультисервер: Velocity + Paper

Реальная мощь Docker раскрывается при мультисерверном сетапе. Один docker-compose.yml описывает всю инфраструктуру.

version: "3.8"

services:
  proxy:
    image: itzg/bungeecord:latest
    container_name: mc-velocity
    restart: unless-stopped
    environment:
      TYPE: VELOCITY
      MEMORY: "512M"
    volumes:
      - ./velocity-data:/server
    ports:
      - "25565:25577"
    deploy:
      resources:
        limits:
          memory: 1G

  lobby:
    image: itzg/minecraft-server:latest
    container_name: mc-lobby
    restart: unless-stopped
    environment:
      EULA: "TRUE"
      TYPE: PAPER
      VERSION: "1.21.4"
      MEMORY: "2G"
      ONLINE_MODE: "FALSE"
      SERVER_PORT: "25566"
    volumes:
      - ./lobby-data:/data
    expose:
      - "25566"
    deploy:
      resources:
        limits:
          memory: 3G

  survival:
    image: itzg/minecraft-server:latest
    container_name: mc-survival
    restart: unless-stopped
    environment:
      EULA: "TRUE"
      TYPE: PAPER
      VERSION: "1.21.4"
      MEMORY: "6G"
      ONLINE_MODE: "FALSE"
      SERVER_PORT: "25567"
    volumes:
      - ./survival-data:/data
    expose:
      - "25567"
    deploy:
      resources:
        limits:
          memory: 8G

  minigames:
    image: itzg/minecraft-server:latest
    container_name: mc-minigames
    restart: unless-stopped
    environment:
      EULA: "TRUE"
      TYPE: PAPER
      VERSION: "1.21.4"
      MEMORY: "4G"
      ONLINE_MODE: "FALSE"
      SERVER_PORT: "25568"
    volumes:
      - ./minigames-data:/data
    expose:
      - "25568"
    deploy:
      resources:
        limits:
          memory: 6G

Обратите внимание: Paper-серверы используют expose вместо ports - они не доступны извне, только через Velocity. Прокси публикует порт 25565, принимает подключения и маршрутизирует на внутренние серверы по Docker DNS (имена сервисов: lobby, survival, minigames).

В velocity.toml серверы указываются по имени Docker-сервиса:

[servers]
lobby = "lobby:25566"
survival = "survival:25567"
minigames = "minigames:25568"
try = ["lobby"]

Docker автоматически резолвит lobby в IP контейнера. Если контейнер перезапустится и получит новый IP, DNS обновится.

Бэкапы Docker-серверов

Бэкап сервера в Docker - это бэкап bind mount директории. Но есть важный момент: нельзя копировать файлы мира, пока сервер пишет в них.

Правильный подход:

#!/bin/bash
# backup.sh

BACKUP_DIR="/backups/minecraft"
DATE=$(date +%Y%m%d_%H%M%S)

# Отключаем автосохранение и делаем финальный save
docker exec mc-survival rcon-cli save-off
docker exec mc-survival rcon-cli save-all
sleep 5

# Копируем данные
tar czf "$BACKUP_DIR/survival-$DATE.tar.gz" ./survival-data/

# Включаем автосохранение обратно
docker exec mc-survival rcon-cli save-on

echo "Backup completed: survival-$DATE.tar.gz"

Образ itzg/minecraft-server включает rcon-cli - можно выполнять серверные команды без подключения к консоли контейнера.

Для автоматизации - cron:

0 */4 * * * /opt/minecraft/backup.sh >> /var/log/mc-backup.log 2>&1

Бэкап каждые 4 часа. Для крупных серверов рассмотрите инкрементальные бэкапы через borgbackup.

Безопасность

Docker-контейнеры по умолчанию запускаются от root внутри контейнера. Для Minecraft-сервера это не критично (контейнер изолирован), но лучше перестраховаться.

Непривилегированный пользователь. Образ itzg/minecraft-server поддерживает UID/GID:

environment:
  UID: 1000
  GID: 1000

Read-only файловая система. Можно сделать корневую ФС контейнера read-only, оставив write-доступ только для данных:

services:
  minecraft:
    image: itzg/minecraft-server:latest
    read_only: true
    tmpfs:
      - /tmp
    volumes:
      - ./server-data:/data

Ограничение capabilities. Убираем лишние Linux capabilities:

services:
  minecraft:
    image: itzg/minecraft-server:latest
    cap_drop:
      - ALL
    cap_add:
      - CHOWN
      - SETUID
      - SETGID

Не используйте --privileged. Никогда. Это даёт контейнеру полный доступ к хосту.

Обновляйте образы. docker-compose pull && docker-compose up -d - обновит образы и пересоздаст контейнеры с новыми версиями.

Когда Docker не нужен

Docker - не универсальное решение. Вот ситуации, когда он добавляет сложности без пользы:

Один сервер на выделенной машине. Если у вас одна VPS с одним Minecraft-сервером, Docker даёт минимум преимуществ. Java и так изолирована в JVM, а Docker добавляет слой абстракции. Проще установить Java напрямую и запускать через systemd.

Критичная производительность. Если вы выжимаете каждый тик из сервера на 200+ игроков, любой оверхед имеет значение. Docker на bridge-сети добавляет latency. Overlay FS добавляет задержку при записи. cgroups добавляют CPU scheduling overhead. На host mode с bind mounts разница минимальна, но она есть.

Отсутствие DevOps-опыта. Если вы не знакомы с Docker, отладка проблем внутри контейнера будет сложнее, чем на голой машине. Логи в другом месте, файловая система многослойная, сеть виртуальная. Начните с изучения Docker на менее критичных проектах.

Shared-хостинг. На панелях типа Pterodactyl Docker уже используется под капотом. Добавлять ещё один слой Docker поверх не имеет смысла.

Полезные команды

Быстрый справочник для ежедневной работы:

# Статус контейнеров
docker-compose ps

# Логи сервера (последние 100 строк, в реальном времени)
docker-compose logs -f --tail=100 survival

# Консоль сервера через rcon
docker exec mc-survival rcon-cli

# Перезапуск одного сервера без остановки остальных
docker-compose restart survival

# Обновление образов
docker-compose pull
docker-compose up -d

# Использование ресурсов
docker stats

# Копирование плагина в контейнер
cp my-plugin.jar ./survival-data/plugins/
docker-compose restart survival

Итого

Docker отлично подходит для мультисерверных сетапов, тестовых окружений и ситуаций, где важна воспроизводимость. Образ itzg/minecraft-server покрывает 95% потребностей. Host networking убирает сетевой оверхед, bind mounts дают прямой доступ к файлам, а cgroups защищают от утечек памяти в плагинах.

Но если у вас один сервер на одной машине - обычный systemd-сервис будет проще и немного производительнее. Docker решает проблемы масштабирования и воспроизводимости. Если этих проблем нет, решение ищет проблему.

Начните с docker-compose для тестового сервера. Погоняйте его неделю, посмотрите на производительность, привыкните к воркфлоу. Если всё устраивает - мигрируйте продакшен. Если нет - ничего не потеряете, файлы мира можно перенести обратно на голый сервер за пару минут.


Sunucunuzu DDoS Saldırılarından Koruyun

5 dakikada kurulumla ücretsiz koruma. 1 TB bant genişliği dahil.

Ücretsiz Deneyin


İlgili Makaleler