Equipe Somente Marmotas Troopers

Time Somente Marmotas Troopers (SMT)

Ideia geral

Decidimos automatizar o processo de hospedar um servidor de Minecraft utilizando um terraform apply.

Primeiros passos

Inicialmente, buscamos entender como funciona a hospedagem de um servidor do jogo, tentando realizar o processo de forma manual. Assim, subimos uma maquina virtual na MGC e instalamos as dependências necessárias.
Após verificar o funcionamento, buscamos automatizar o processo com um script para realizar as instalações e configurações necessárias, iniciando o servidor de forma automática.

Configuração inicial

  • Criamos um arquivo main.tf da seguinte forma
terraform {
  required_providers {
    mgc = {
      source = "MagaluCloud/mgc"
    }
  }
}

provider "mgc" {
  alias = "nordeste"
  region = "br-ne1"
  object_storage = {
    key_pair = {
      key_id = "a6f14018-f700-4731-99d6-8c8649904fe9"
      key_secret = "e214e0a7-1282-4d99-b10f-9b51dc1c6a21"
    }
  }
}

resource "mgc_virtual_machine_instances" "minecraft_vm_marmotas" {
  provider       = "mgc"
  name           = "minecraft_vm_marmotas"
  name_is_prefix = true
  machine_type   = {
    name = "BV2-8-40"
  }
  image = {
    name = "cloud-ubuntu-22.04 LTS"
  }
  network = {
    associate_public_ip = true
    interface = {
      security_groups = [{
        id = "4aa1a237-2d57-439b-bf6a-177ddbace4cb"
      }]
    }
  }
  ssh_key_name = "Danilo"
}
  • Esse código utilizou como base a versão de criação de um servidor de Factório (encontrado no fórum do hackathon)
  • Porém, enfrentamos problemas ao tentar acessar inicialmente a VM criada, visto que o IP não era facilmente encontrado através do console, até depararmos com a necessidade de acrescentar o seguinte trecho
output "ip" {
  value = mgc_virtual_machine_instances.minecraft_vm_marmotas.network.public_address
}
  • Esse trecho permite retornar o IP da VM assim que ela é criada, possibilitando fazer o SSH e acessá-la internamente
  • Para automatizar o processo, criamos o seguinte script em um arquivo install_minecraft.sh
#!/bin/bash

# Atualiza o sistema
sudo apt update -y && sudo apt upgrade -y

# Adiciona o repositório do Amazon Corretto
wget -O- https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg
echo 'deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main' | sudo tee /etc/apt/sources.list.d/corretto.list

# Instala o Java 21 Amazon Corretto
sudo apt update -y
sudo apt install -y java-21-amazon-corretto-jdk

# Baixa o arquivo server.jar do Minecraft
wget -O server.jar https://piston-data.mojang.com/v1/objects/45810d238246d90e811d896f87b14695b7fb6839/server.jar

# Aceita a EULA
touch eula.txt
chmod +x eula.txt
echo "eula=true" > eula.txt

# Cria o arquivo server.properties
touch server.properties
echo "#Minecraft server properties\n \
#Thu Apr 30 23:42:29 UTC 2020\n \
spawn-protection=16\n \
max-tick-time=60000\n \
query.port=25565\n \
generator-settings=\n \
force-gamemode=false\n \
allow-nether=true\n \
enforce-whitelist=false\n \
gamemode=survival\n \
broadcast-console-to-ops=true\n \
enable-query=false\n \
player-idle-timeout=0\n \
difficulty=normal\n \
spawn-monsters=true\n \
broadcast-rcon-to-ops=true\n \
op-permission-level=4\n \
pvp=true\n \
snooper-enabled=true\n \
level-type=default\n \
hardcore=false\n \
enable-command-block=false\n \
max-players=20\n \
network-compression-threshold=256\n \
resource-pack-sha1=\n \
max-world-size=29999984\n \
function-permission-level=2\n \
rcon.port=25575\n \
server-port=25565\n \
server-ip= \n \
spawn-npcs=true\n \s
allow-flight=false\n \
level-name=world\n \
view-distance=10\n \
resource-pack=\n \
spawn-animals=true\n \
white-list=false\n \
rcon.password=\n \
generate-structures=true\n \
online-mode=true\n \
max-build-height=256\n \
level-seed=\n \
prevent-proxy-connections=false\n \
use-native-transport=true\n \
motd=A Minecraft Server\n \
enable-rcon=false" >> server.properties

# Executa novamente o servidor com a EULA aceita
java -Xmx1024M -Xms1024M -jar server.jar nogui
  • Esse código cria de forma estática o servidor utilizando a versão 1.21.3 do jogo, com configurações fixas de número de jogadores, dificuldade do jogo, etc.
  • Assim, foi possível rodar pela primeira vez o servidor acessando no jogo o ip da VM no formato ip_vm:25565

Processo de automação

  • A fim de automatizar totalmente, buscamos integrar o script utilizado diretamente com o terraform, para roda-lo assim que a VM fosse criada. Entretanto, tivemos alguns problemas no caminho…

Problema 1 - user_data not found :frowning:

  • Tentamos utilizar na criação da VM o parâmetro user_data da seguinte forma
resource "mgc_virtual_machine_instances" "minecraft_vm_marmotas" {
  provider       = "mgc"
  name           = "minecraft_vm_marmotas"
  name_is_prefix = true
  machine_type   = {
    name = "BV2-8-40"
  }
  image = {
    name = "cloud-ubuntu-22.04 LTS"
  }
  network = {
    associate_public_ip = true
    interface = {
      security_groups = [{
        id = "4aa1a237-2d57-439b-bf6a-177ddbace4cb"
      }]
    }
  }
  ssh_key_name = "Danilo"
  user_data = file("scripts/install_minecraft.sh")
}
  • Teoricamente isso deveria funcionar, mas ao tentar rodar o terraform nos deparamos com o seguinte erro:
An argument named "user_data" is not expected here.
  • Tentamos utilizar código direto em Base64 no lugar do path, porém o erro persistiu.
  • Ao analisarmos a documentação da Magalu Cloud (Virtual Machines | Docs Magalu Cloud) encontramos a existência do parâmetro user_data na criação de instâncias. Porém, com a ajuda da equipe de suporte da Magalu durante o Hackathon, percebemos que a documentação do registry terraform (Terraform Registry) alegava a não existência do parâmetro, justificando o erro. Ou seja, só era possível utilizar o user_data através da criação da VM pela CLI.

Solução do problema 1

  • Conversando com outros grupos, descobrimos que era possível a execução de código assim que criada a VM utilizando provisioner e connection.
provisioner "file" {
    source      = "./scripts/install_minecraft.sh"
    destination = "/home/ubuntu/install_minecraft.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /home/ubuntu/install_minecraft.sh",
      "sudo /home/ubuntu/install_minecraft.sh"
    ]
  }

  connection {
    type        = "ssh"
    user        = "ubuntu"
    private_key = file("./ssh-magalu")
    host        = self.network.public_address
  }
  • Nesse caso, o arquivo ssh-magalu contém a chave privada criada localmente, para que fosse possível rodar o ssh de forma automática
  • Ao rodar, tivemos um problema…

Problema 2 - Pending kernel upgrade

  • Por algum motivo, ao realizar o sudo apt update -y && sudo apt upgrade -y apareceu uma mensagem no terminal referente a um upgrade pendente do kernel, sendo necessário agir manualmente para continuar o processo.

Solução do problema 2

  • Para contornar esse problema, adicionamos no começo do script o comando sudo apt -y remove needrestart, que age como um workaround para a nossa situação.

Problema 3 - Still creating… (forever)

  • Tentando rodar a VM percebemos que o script passou a ser executado corretamente, mas continuamente a VM mantinha o status de still creating, sendo mostrado a cada 10 segundos, mesmo com o servidor deployed.
  • Desse modo, não era possível conectar no servidor do jogo, pois para isso era necessário o endereço de IP da VM, que só era mostrado após o fim da criação da VM, através do output "ip" anteriormente definido.

Solução do problema 3

  • Após analisar diversos pontos que poderiam causar o problema, entendemos que o motivo de rodar infinitamente se devia ao fato do servidor se manter ativo em primeiro plano, mantendo o foco do terminal, impedindo a finalização da criação.
  • Então, apesar de um tempo relativamente grande para analisar e de fato entender o problema, a solução foi simples: rodar o servidor em background, atualizando uma linha do arquivo de script com:
java -Xmx1024M -Xms1024M -jar server.jar nogui &

Variáveis

  • Agora o problema estava resolvido e funcional, sendo possível rodar de forma automática o servidor de Minecraft.
  • Deste modo, foram criadas variáveis para definir dinamicamente a versão do jogo, o número de players, a porta em que seria estabelecida a conexão, a dificuldade de jogo, o modo de jogo, mínimo e máximo de ram para o servidor do jogo.
variable "minecraft_version" {
  description = "Versão do Minecraft Server (https://www.minecraft.net/en-us/download/server/)"
  type        = string
  default     = "https://piston-data.mojang.com/v1/objects/45810d238246d90e811d896f87b14695b7fb6839/server.jar" 
}

variable "max_players" {
  description = "Número máximo de jogadores"
  type        = number
  default     = 20
}

variable "port" {
  description = "Porta do servidor (1 to 65535)"
  type        = number
  default     = 25565
}

variable "difficulty" {
  description = "Dificuldade do servidor (peaceful/ease/normal/hard)"
  type        = string
  default     = "normal"
}

variable "gamemode" {
  description = "Modo de jogo (creative/adventure/survival/spectator)"
  type        = string
  default     = "survival"
}

variable "machine_type" {
  description = "Tipe de maquina virtual (padrão BV2-8-40)"
  type        = string
  default     = "BV2-8-40"
}

variable "min_ram" {
  description = "Mínimo de ram para o servidor do jogo"
  type        = string
  default     = "-Xms1024M"
}

variable "max_ram" {
  description = "Máximo de ram para o servidor do jogo"
  type        = string
  default     = "-Xmx1024M"
}

Código final

Já automatizado, por fim os códigos ficaram:

main.tf

terraform {
  required_providers {
    mgc = {
      source = "MagaluCloud/mgc"
    }
  }
}

provider "mgc" {
  alias = "nordeste"
  region = "br-ne1"
  object_storage = {
    key_pair = {
      key_id = "a6f14018-f700-4731-99d6-8c8649904fe9"
      key_secret = "e214e0a7-1282-4d99-b10f-9b51dc1c6a21"
    }
  }
}

variable "minecraft_version" {
  description = "Versão do Minecraft Server (https://www.minecraft.net/en-us/download/server/)"
  type        = string
  default     = "https://piston-data.mojang.com/v1/objects/45810d238246d90e811d896f87b14695b7fb6839/server.jar" 
}

variable "max_players" {
  description = "Número máximo de jogadores"
  type        = number
  default     = 20
}

variable "port" {
  description = "Porta do servidor (1 to 65535)"
  type        = number
  default     = 25565
}

variable "difficulty" {
  description = "Dificuldade do servidor (peaceful/ease/normal/hard)"
  type        = string
  default     = "normal"
}

variable "gamemode" {
  description = "Modo de jogo (creative/adventure/survival/spectator)"
  type        = string
  default     = "survival"
}

variable "machine_type" {
  description = "Tipe de maquina virtual (padrão BV2-8-40)"
  type        = string
  default     = "BV2-8-40"
}

variable "machine_type" {
  description = "Tipe de maquina virtual (padrão BV2-8-40)"
  type        = string
  default     = "BV2-8-40"
}

variable "min_ram" {
  description = "Mínimo de ram para o servidor do jogo"
  type        = string
  default     = "-Xms1024M"
}

variable "max_ram" {
  description = "Máximo de ram para o servidor do jogo"
  type        = string
  default     = "-Xmx1024M"
}


resource "mgc_virtual_machine_instances" "minecraft_vm_marmotas" {
  provider       = "mgc"
  name           = "minecraft_vm_marmotas"
  name_is_prefix = true
  machine_type   = {
    name = var.machine_type
  }
  image = {
    name = "cloud-ubuntu-22.04 LTS"
  }
  network = {
    associate_public_ip = true
    interface = {
      security_groups = [{
        id = "4aa1a237-2d57-439b-bf6a-177ddbace4cb"
      }]
    }
  }
  ssh_key_name = "Danilo"

  provisioner "file" {
    source      = "./scripts/install_minecraft.sh"
    destination = "/home/ubuntu/install_minecraft.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /home/ubuntu/install_minecraft.sh",
      "sudo /home/ubuntu/install_minecraft.sh ${var.minecraft_version} ${var.max_players} ${var.port} ${var.difficulty} ${var.gamemode} ${var.min_ram} ${var.max_ram}"
    ]
  }

  connection {
    type        = "ssh"
    user        = "ubuntu"
    private_key = file("./ssh-magalu")
    host        = self.network.public_address
  }
}

output "ip" {
  value = mgc_virtual_machine_instances.minecraft_vm_marmotas.network.public_address
}

scripts/install_minecraft.sh

#!/bin/bash

# Parâmetros de entrada do Terraform
MINECRAFT_VERSION=$1
MAX_PLAYERS=$2
PORT=$3
DIFFICULTY=$4
GAMEMODE=$5
MIN_RAM=$6
MAX_RAM=$7

# Atualiza o sistema
sudo apt -y remove needrestart
sudo apt update -y && sudo apt upgrade -y
sudo ufw allow ${PORT}

# Adiciona o repositório do Amazon Corretto
wget -O- https://apt.corretto.aws/corretto.key | sudo gpg --dearmor -o /usr/share/keyrings/corretto-keyring.gpg
echo 'deb [signed-by=/usr/share/keyrings/corretto-keyring.gpg] https://apt.corretto.aws stable main' | sudo tee /etc/apt/sources.list.d/corretto.list

# Instala o Java 21 Amazon Corretto
sudo apt update -y
sudo apt install -y java-21-amazon-corretto-jdk

# Baixa o arquivo server.jar do Minecraft com a versão especificada
wget -O server.jar ${MINECRAFT_VERSION}

# Aceita a EULA
touch eula.txt
chmod +x eula.txt
echo "eula=true" > eula.txt

# Cria o arquivo server.properties com o número máximo de jogadores especificado
cat <<EOL > server.properties
#Minecraft server properties
#Thu Apr 30 23:42:29 UTC 2020
spawn-protection=16
max-tick-time=60000
query.port=25565
generator-settings=
force-gamemode=false
allow-nether=true
enforce-whitelist=false
gamemode=${GAMEMODE}
broadcast-console-to-ops=true
enable-query=false
player-idle-timeout=0
difficulty=${DIFFICULTY}
spawn-monsters=true
broadcast-rcon-to-ops=true
op-permission-level=4
pvp=true
snooper-enabled=true
level-type=default
hardcore=false
enable-command-block=false
max-players=${MAX_PLAYERS}
network-compression-threshold=256
resource-pack-sha1=
max-world-size=29999984
function-permission-level=2
rcon.port=25575
server-port=${PORT}
server-ip=
spawn-npcs=true
allow-flight=false
level-name=world
view-distance=10
resource-pack=
spawn-animals=true
white-list=false
rcon.password=
generate-structures=true
online-mode=true
max-build-height=256
level-seed=
prevent-proxy-connections=false
use-native-transport=true
motd=A Minecraft Server
enable-rcon=false
EOL

# Executa o servidor Minecraft com as configurações especificadas
java ${MAX_RAM} ${MIN_RAM} -jar server.jar nogui &

Agora, é só rodar

terraform init
terraform apply

Pegar o ip resultante no console após o fim da execução

Outputs:

ip = "169.150.0.66"

Adicionar o servidor no modo multiplayer do Minecraft

Se divertir :slight_smile:

4 curtidas

Fenomenal!! Bom trabalho galera!