Minecraft в kubernetes
Всем привет, в этой статье я поделюсь своим опытом запуска Minecraft в kubernetes.
Основная проблема, возникающая при попытке запустить Minecraft в Kubernetes, заключается в том, что во время работы Minecraft происходит работа с файлами на локальном диске, а такой подход в Kubernetes является нежелательным, поскольку в случае сбоя ноды (назовем его нода A), на которой развернут под - под не сможет быть создан на другой ноде (нода B), так как известно, что под ранее работал на ноде A и, соответственно, все его постоянные файлы находятся там.
Варианты решения проблем с использованием постоянных файлов
В поисках решения этой проблемы, я определил возможные методы решения:
- отказ от постоянных файлов
- использовать Persistent Volume
- создать сетевой диск
- использовать csi-s3
Давайте подробнее рассмотрим каждый из вариантов
Отказ от постоянных файлов
Плюсы:
- Работает нативно
- Это лучшее решение, т.к. не будет проблем с пересозданием пода на другой ноде в случае сбоя
Минусы:
- Не применимо к текущему приложению
Использовать Persistent Volume
Плюсы:
- Высокая скорость чтения/записи файлов
Минусы:
- Все данные находятся на одной ноде, в случае его поломки pod не сможет подняться
Создать сетевой диск
Плюсы:
- Данные реплицируются на несколько нод
Минусы:
- Более низкая скорость чтения/записи файлов
Использовать csi-s3
Плюсы:
- Данные не находятся в кластере
Минусы:
- Более низкая скорость чтения/записи файлов
- Из-за большого количества вызовов API может получиться высокая цена
Анализ вариантов
Основываясь на полученных данных, мы приходим к выводу, что на данный момент не существует решения, которое могло бы соответствовать нашим требованиям, а именно:
- высокая скорость чтения/записи файлов
- не должно быть привязки к определенной ноде
- минимальные затраты на реализацию данного функционала
Решение проблемы с использованием постоянных данных
Чтобы решить эту проблему, был подготовлен docker контейнер.
Алгоритм работы контейнера
- Добавляются SSH-ключи. Ключи берутся из значений переменных ENV
- Репозиторий сервера клонируется из git с конфигурацией сервера в каталог -
/app
- Последовательности
MYSQL_.+
заменяются во всех файлах в/app/plugins
на их значение ENV - Игровой мир копируется из хранилища s3 и распаковывается
- Игровые плагины и ядро копируются из хранилища s3
- Начинается
/task_manager.py
- который отвечает за выполнение задачcron
, работу сервера и выводsyslog
Cron задачи
В задачах cron в настоящее время настроены следующие задачи:
* * * * * root /git_pull.sh | logger
* * * * * root /git_commit.sh | logger
0 * * * * root /copy_worlds.sh | logger
Информация по каждой задаче:
/git_pull.sh
- выполняетgit pull
в каталоге/app
/git_commit.sh
- создает commit и отправляет его в репозиторий/copy_worlds.sh
- создает копию мира и загружает ее в хранилище s3 вместо существующей
Пример развертывания службы
Настройка ingress
контроллера
Рассмотрим пример с traefik
. Чтобы настроить ingres
контроллер, вам нужно будет выполнить следующую команду:
$ helm upgrade --values traefik-values.yaml traefik traefik/traefik -n kube-system
Контент файла traefik-values.yaml
:
ports:
traefik:
port: 9000
expose: false
exposedPort: 9000
protocol: TCP
web:
port: 8000
expose: true
exposedPort: 80
protocol: TCP
minecraft:
port: 22565
expose: true
exposedPort: 25565
protocol: TCP
websecure:
port: 8443
expose: true
exposedPort: 443
protocol: TCP
http3:
enabled: false
tls:
enabled: true
options: ""
certResolver: ""
domains: []
middlewares: []
metrics:
port: 9100
expose: false
exposedPort: 9100
protocol: TCP
После этого подключения к LoadBalancer
будут разрешены через порт 25565
$ kubectl get svc -n kube-system | grep -v "none"
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
traefik LoadBalancer 10.43.2.50 100.10.0.11 25565:30224/TCP,80:31380/TCP,443:30226/TCP 2d21h
Deploy
Пример окончательного манифеста, содержащего требуемую конфигурацию:
---
apiVersion: v1
kind: Namespace
metadata:
name: minecraft
---
apiVersion: v1
kind: Secret
metadata:
name: minecraft-secret
namespace: minecraft
type: Opaque
data:
SSH_KEY_PUBLIC: "SECRET"
SSH_KEY_PRIVATE: "SECRET"
MYSQL_ROOT_PASSWORD: "SECRET"
MYSQL_DBS: "SECRET"
MYSQL_HOST: "SECRET"
MYSQL_USER: "SECRET"
MYSQL_PASSWORD: "SECRET"
S3_BUCKET: "SECRET"
S3_ACCESS_KEY: "SECRET"
S3_ACCESS_KEY_ID: "SECRET"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: some_worlds_name
namespace: minecraft
spec:
selector:
matchLabels:
app: some_worlds_name
strategy:
type: Recreate
template:
metadata:
namespace: minecraft
labels:
app: some_worlds_name
spec:
imagePullSecrets:
- name: github-registry
containers:
- image: ghcr.io/oldtyt/docker_minecraft
imagePullPolicy: Always
name: some_worlds_name
resources:
requests:
cpu: 1000m
memory: 5G
limits:
memory: 6G
cpu: 1200m
env:
- name: XMX
value: "5G"
- name: "XMS"
value: "512M"
- name: MYSQL_HOST
valueFrom:
secretKeyRef:
name: minecraft-secret
key: MYSQL_HOST
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: minecraft-secret
key: MYSQL_USER
- name: MYSQL_DB
valueFrom:
secretKeyRef:
name: minecraft-secret
key: MYSQL_USER
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: minecraft-secret
key: MYSQL_PASSWORD
- name: S3_BUCKET
valueFrom:
secretKeyRef:
name: minecraft-secret
key: S3_BUCKET
- name: S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: minecraft-secret
key: S3_ACCESS_KEY
- name: S3_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: minecraft-secret
key: S3_ACCESS_KEY_ID
- name: SSH_KEY_PRIVATE
valueFrom:
secretKeyRef:
name: minecraft-secret
key: SSH_KEY_PRIVATE
- name: SSH_KEY_PUBLIC
valueFrom:
secretKeyRef:
name: minecraft-secret
key: SSH_KEY_PUBLIC
- name: PLUGINS_LIST
value: "some,plugins,list"
- name: GIT_REPO
value: "git@github.com:USER/REPO.git"
- name: KERNEL
value: "KERNEL"
- name: "WORLDS"
value: "some_worlds_name"
---
apiVersion: v1
kind: Service
metadata:
name: some_worlds_name
namespace: minecraft
labels:
env: prod
app: some_worlds_name
owner: OldTyT
spec:
ports:
- name: minecraft
targetPort: 25565
port: 25565
protocol: TCP
selector:
app: some_worlds_name
Команда для развертывания манифеста:
$ kubectl apply -f minecraft.yaml