Docker dla serwerów Minecraft: plusy, minusy, pułapki

Docker dla serwerów Minecraft: plusy, minusy, pułapki

Załóżmy, że masz trzy serwery Minecraft: lobby, survival i minigry. Każdy wymaga swojej wersji Javy, swoich pluginów, swoich ustawień JVM. Aktualizujesz jeden - psujesz drugi. Znajoma historia? Docker rozwiązuje dokładnie ten problem. Każdy serwer żyje w izolowanym kontenerze ze swoim środowiskiem i nie przeszkadzają sobie nawzajem.

Ale Docker to nie srebrna kula. Ma realne ograniczenia, szczególnie dla serwerów gry, gdzie liczy się każda milisekunda. W tym artykule rozbierzemy, kiedy Docker jest naprawdę przydatny, a kiedy tworzy więcej problemów, niż rozwiązuje.

Po co Docker dla Minecrafta

Główną ideą Dockera jest izolacja. Każdy kontener zawiera wszystko, co potrzebne do działania aplikacji: system operacyjny, Javę, serwer, pluginy. Jeśli kontener padnie, pozostałe nadal działają. Jeśli trzeba zaktualizować Javę z 17 na 21 dla jednego serwera - nie tknie to pozostałych.

Powtarzalność. Obraz Dockera zawiera ustalone środowisko. To, co działa na twojej maszynie testowej, będzie działać tak samo na produkcji. Już nie ma "a u mnie działało" - jeśli obraz się zbudował, uruchomi się identycznie na każdej maszynie z Dockerem.

Prostota deploymentu. Nowy serwer to jeden docker-compose up -d. Nie trzeba instalować Javy, konfigurować zmiennych środowiskowych, ręcznie ściągać plików jar. Wszystko opisane w docker-compose.yml i każdy członek zespołu może wdrożyć identyczne środowisko.

Izolacja zasobów. Docker używa cgroups do ograniczania CPU i pamięci. Jeśli serwer minigier nagle zjadł całą pamięć przez wyciek w pluginie, nie pociągnie za sobą lobby i survivala. OOM killer zabije tylko jeden kontener.

Proste backupy. Dane trzymane są w Docker volumes albo bind mountach. Backup to kopiowanie katalogu. Przywracanie to podmiana katalogu i restart kontenera. Żadnych skomplikowanych procedur.

Obraz itzg/minecraft-server

Nie trzeba pisać swojego Dockerfile'a od zera. Obraz itzg/minecraft-server to de facto standard do odpalania MC w Dockerze. Wspiera Paper, Spigot, Fabric, Forge, Velocity, BungeeCord i dziesiątki innych platform serwerowych.

Minimalny start:

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

Kontener ściągnie Paper 1.21.4, zaakceptuje EULA, utworzy świat i zacznie nasłuchiwać na porcie 25565. Dane zachowają się w volume mc-data.

Przydatne zmienne środowiskowe:

  • MEMORY=4G - przydziel 4 GB dla JVM (ustawia zarówno -Xms, jak i -Xmx)
  • TYPE=PAPER - typ serwera (PAPER, SPIGOT, FABRIC, FORGE, VELOCITY, BUNGEECORD)
  • VERSION=1.21.4 - wersja Minecrafta
  • ONLINE_MODE=FALSE - dla serwerów za proxy (Velocity/BungeeCord)
  • OPS=player1,player2 - lista operatorów
  • DIFFICULTY=hard - trudność
  • VIEW_DISTANCE=10 - zasięg widzenia
  • MAX_PLAYERS=100 - limit graczy
  • JVM_OPTS=-XX:+UseG1GC -XX:+ParallelRefProcEnabled - dodatkowe flagi JVM

Docker Compose: jeden serwer

Na produkcji zawsze używaj docker-compose zamiast gołego docker run. Config w pliku łatwiej utrzymać, wersjonować i przekazywać kolegom.

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"

Zwróć uwagę na deploy.resources. Tu ustawiamy twarde limity: kontener nie będzie mógł wykorzystać więcej niż 8 GB RAM i 4 rdzeni CPU. Przy tym rezerwujemy minimum 6 GB i 2 rdzenie. Limit pamięci Dockera musi być wyższy niż MEMORY dla JVM - Java zużywa więcej niż rozmiar heapu (metaspace, natywna pamięć, wątki).

Volumes: światy, pluginy, configi

Prawidłowa organizacja volumes to klucz do wygodnego zarządzania. Masz dwie opcje: Docker volumes i bind mounty.

Bind mounty (polecam dla MC) - mapowanie konkretnego katalogu hosta do kontenera:

volumes:
  - ./server-data:/data

Wszystkie pliki serwera będą w ./server-data na hoście. Można edytować configi bezpośrednio, kopiować pluginy przez scp, robić backupy zwykłym rsyncem.

Docker volumes - zarządzane przez Dockera storage:

volumes:
  - mc-data:/data

volumes:
  mc-data:

Dane trzymane są w /var/lib/docker/volumes/mc-data/_data. Bardziej izolowane, ale mniej wygodne dla bezpośredniego dostępu.

Dla serwerów Minecraft bind mounty są praktyczniejsze. Typowa struktura:

./server-data/
  world/           # glowny swiat
  world_nether/    # nether
  world_the_end/   # end
  plugins/         # pliki JAR i configi pluginow
  server.properties
  paper-global.yml
  paper-world-defaults.yml
  ops.json
  whitelist.json

Żeby dodać plugin, po prostu skopiuj JAR do ./server-data/plugins/ i zrestartuj kontener.

Sieć: host vs bridge

To krytycznie ważny wybór dla serwerów gry.

Bridge (domyślnie). Docker tworzy wirtualną sieć. Ruch idzie przez NAT. Dla większości aplikacji webowych to normalne. Dla Minecrafta dodaje opóźnienie. NAT obrabia każdy pakiet i przy 100 graczach jest to odczuwalne. Plus: port-forwarding, izolacja sieci.

Host mode. Kontener używa stacku sieciowego hosta bezpośrednio. Nie ma NAT, nie ma narzutu. Pakiety lecą prosto do procesu Javy. Minus: brak izolacji sieci, kontener zajmuje porty hosta.

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

Dla serwerów gry używaj host mode. Różnica w latency 1-3 ms może się wydawać drobiazgiem, ale przy 20 tickach na sekundę i dziesiątkach graczy się kumuluje. Bridge mode jest uzasadniony, jeśli potrzebujesz izolacji sieci między kontenerami albo jeśli na jednej maszynie masz kilka serwerów na różnych portach.

Wydajność: Java w kontenerach

Java w Dockerze działa dobrze, ale są niuanse.

Pamięć. JVM widzi limity cgroups i prawidłowo określa dostępną pamięć (w Javie 10+ od razu). Jeśli ustawisz MEMORY=6G i limit Dockera 8G, wszystko będzie poprawne. Ale nie ustawiaj limitu Dockera równego rozmiarowi heapu - JVM potrzebuje pamięci ponad heap (200-500 MB na metaspace, wątki, natywne bufory).

CPU. Docker używa CPU shares i CFS quota. Domyślnie kontenery dzielą CPU proporcjonalnie. Twardy limit (cpus: "4.0") może prowadzić do throttlingu - pauza GC nie będzie mogła skorzystać z więcej niż 4 rdzeni, nawet jeśli są wolne. Dla pojedynczego serwera lepiej nie stawiać twardego limitu CPU, a używać cpu_shares do priorytetyzacji.

Dysk. Overlay2 (standardowy system plików Dockera) dodaje minimalny narzut przy odczycie. Ale przy aktywnym zapisie (zapisywanie świata, logi) różnica może być zauważalna. Bind mounty działają bez overlay - bezpośredni dostęp do systemu plików hosta. Jeszcze jeden powód, żeby używać bind mountów dla danych.

Sieć. O bridge vs host już mówiliśmy. Ważne: jeśli używasz bridge, włączenia jumbo frames w sieci Dockera nie da się zrobić - MTU jest ustalone. Przy dużej ilości chunków lecących do gracza może to zwiększyć liczbę pakietów.

Multiserwer: Velocity + Paper

Prawdziwa moc Dockera otwiera się przy setupie multiserwerowym. Jeden docker-compose.yml opisuje całą infrastrukturę.

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

Zwróć uwagę: serwery Paper używają expose zamiast ports - nie są dostępne z zewnątrz, tylko przez Velocity. Proxy publikuje port 25565, przyjmuje połączenia i kieruje je na wewnętrzne serwery po Docker DNS (nazwy serwisów: lobby, survival, minigames).

W velocity.toml serwery wskazuje się po nazwie serwisu Dockera:

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

Docker automatycznie rezolwuje lobby na IP kontenera. Jeśli kontener się zrestartuje i dostanie nowy IP, DNS się zaktualizuje.

Backupy serwerów w Dockerze

Backup serwera w Dockerze to backup katalogu bind mount. Ale jest ważny moment: nie można kopiować plików świata, póki serwer w nich pisze.

Prawidłowe podejście:

#!/bin/bash
# backup.sh

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

# Wylaczamy autozapis i robimy finalny save
docker exec mc-survival rcon-cli save-off
docker exec mc-survival rcon-cli save-all
sleep 5

# Kopiujemy dane
tar czf "$BACKUP_DIR/survival-$DATE.tar.gz" ./survival-data/

# Wlaczamy autozapis z powrotem
docker exec mc-survival rcon-cli save-on

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

Obraz itzg/minecraft-server zawiera rcon-cli - można wykonywać komendy serwera bez podłączania się do konsoli kontenera.

Do automatyzacji - cron:

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

Backup co 4 godziny. Dla dużych serwerów rozważ backupy inkrementalne przez borgbackup.

Bezpieczeństwo

Kontenery Dockera domyślnie uruchamiają się jako root wewnątrz kontenera. Dla serwera Minecraft nie jest to krytyczne (kontener jest izolowany), ale lepiej się zabezpieczyć.

Nieuprzywilejowany użytkownik. Obraz itzg/minecraft-server wspiera UID/GID:

environment:
  UID: 1000
  GID: 1000

System plików tylko do odczytu. Można zrobić główny FS kontenera read-only, zostawiając dostęp do zapisu tylko dla danych:

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

Ograniczenie capabilities. Usuwamy zbędne capabilities Linuksa:

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

Nie używaj --privileged. Nigdy. To daje kontenerowi pełny dostęp do hosta.

Aktualizuj obrazy. docker-compose pull && docker-compose up -d - zaktualizuje obrazy i odtworzy kontenery z nowymi wersjami.

Kiedy Docker nie jest potrzebny

Docker to nie uniwersalne rozwiązanie. Oto sytuacje, gdy dodaje komplikacji bez korzyści:

Jeden serwer na dedykowanej maszynie. Jeśli masz jeden VPS z jednym serwerem Minecraft, Docker daje minimum korzyści. Java i tak jest izolowana w JVM, a Docker dodaje warstwę abstrakcji. Łatwiej zainstalować Javę bezpośrednio i uruchamiać przez systemd.

Krytyczna wydajność. Jeśli wyciskasz każdy tick z serwera na 200+ graczy, każdy narzut ma znaczenie. Docker na sieci bridge dodaje latency. Overlay FS dodaje opóźnienie przy zapisie. cgroups dodają narzut CPU scheduling. Na host mode z bind mountami różnica jest minimalna, ale jest.

Brak doświadczenia DevOps. Jeśli nie znasz Dockera, debugowanie problemów wewnątrz kontenera będzie trudniejsze niż na gołej maszynie. Logi są w innym miejscu, system plików wielowarstwowy, sieć wirtualna. Zacznij od nauki Dockera na mniej krytycznych projektach.

Shared hosting. Na panelach typu Pterodactyl Docker jest już używany pod maską. Dodawanie kolejnej warstwy Dockera na wierzchu nie ma sensu.

Przydatne komendy

Szybka ściąga do codziennej pracy:

# Status kontenerow
docker-compose ps

# Logi serwera (ostatnie 100 linii, w czasie rzeczywistym)
docker-compose logs -f --tail=100 survival

# Konsola serwera przez rcon
docker exec mc-survival rcon-cli

# Restart jednego serwera bez zatrzymania pozostalych
docker-compose restart survival

# Aktualizacja obrazow
docker-compose pull
docker-compose up -d

# Uzycie zasobow
docker stats

# Kopiowanie pluginu do kontenera
cp my-plugin.jar ./survival-data/plugins/
docker-compose restart survival

Podsumowanie

Docker świetnie nadaje się do setupów multiserwerowych, środowisk testowych i sytuacji, gdzie ważna jest powtarzalność. Obraz itzg/minecraft-server pokrywa 95% potrzeb. Host networking zdejmuje narzut sieciowy, bind mounty dają bezpośredni dostęp do plików, a cgroups chronią przed wyciekami pamięci w pluginach.

Ale jeśli masz jeden serwer na jednej maszynie - zwykły serwis systemd będzie prostszy i trochę wydajniejszy. Docker rozwiązuje problemy skalowania i powtarzalności. Jeśli tych problemów nie ma, rozwiązanie szuka problemu.

Zacznij od docker-compose dla serwera testowego. Pogoń go tydzień, zobacz na wydajność, przyzwyczaj się do workflow. Jeśli wszystko pasuje - migruj produkcję. Jeśli nie - nic nie stracisz, pliki świata można przenieść z powrotem na goły serwer w parę minut.


Chroń swój serwer przed atakami DDoS

Darmowa ochrona z konfiguracją w 5 minut. 1 TB ruchu w zestawie.

Wypróbuj za darmo


Powiązane artykuły