Skip to content

Container and Application Architecture

Johnny Knighten edited this page Nov 30, 2023 · 5 revisions

Goal

The features and architecture of this container is highly inspired by the lloesche/valheim-server container image. It has hand's down been one of the best featured and easy to use game server container I have used. There has been a handful of ARK Survival Ascended container projects started since the launch of the game; however, I wasn't really satisfied by the features and configurability. This repo is my attempt to build a "best-of-class" game container that offers many quality of life features to ease server administrations.

Process Management

For those who are new to Docker containers, they are designed around having one long term "primary" process typically with the PID of 1. Although they are designed around this there is nothing stopping you from running many processes inside a container. The big thing you have to know about the "primary" process is that it is the only process inside the container that will directly receive the SIGINT or SIGTERM signal when the container is told to stop (docker stop/docker compose down). When a container is told to stop it sends the stop signals to the "primary" process while the other process are ignorant that they need to stop. To further complicate things, by default, docker only gives containers 10seconds before it sends a SIGKILL signal and kills all processes instantly. So what this means is if we have multiple long running processes we need some way to manage their life-cycle within the container, otherwise they ungracefully die. For some applications, that can be completely okay, but for others (like game servers) we need to gracefully handle how they close. For instance, if you just tamed that hard to get dino, and a split second later the container is terminated, if the container is not setup correctly is could lead to data loss and bye bye dino.

When evaluating the goals of this container there were three big features that screamed we needed some type of process management:

  • The fact that this is a a multiplayer game server
    • We need to exit gracefully to minimize the chance of data loss
  • The need to run multiple process for the entire life of the container
    • The big one being needing to run CRON inside the container to schedule events
  • The need to be able to stop and start specific processes triggered by events
    • Related to the above point, we need to be able to schedule updates, backups, and restarts

I went with Supervisor as it was the one I had experience with in a container based environment. Also with it being Python based it should be relatively easy to extend and develop eventlisteners if needed. Although its good to keep in mind that eventlisteners are not language specific and just mode adhere to an interface.

Supervisor

Supervisor has two primary parts: supervisord and supervisorctl.

Supervisord

Supervisord is the daemon process and is the process responsible for managing all processes that Supervisor is responsible for. When used in a container it is common for Supervisord to be the "primary" process (PID=1). In our case we actually will have another script be the "primary" process due to a need to do some cleanup work that needs to carefully handled by Supervidord before it is stopped. Supervisord is configured by via the supervisord.conf configuration file that defines what process Supervisor will be responsible for. The configuration has details such as: command to execute, log locations, restart behavior, and start/stop/retry timeouts. See here for more documentation on setting up the config file.

Supervisorctl

Supervisorctl is a command line tool that allows processes/users to interact with Supervisord. It allows you to do things such as start/stop/restarts processes, get a processes status, and dynamically add/remove process managed by Supervisord. In our case Supervisorctl will be used to change the state of the container by having processes launch or stop other processes. For instance, or scheduled CRON jobs are simply just scheduled sequences of Supervisorctl commands.

Key Processes

We have a handful of key process which each are their own bash script.

Our core processes are:

  • System Bootstrap
    • This is our PID=1 process and actually launched Supervisord for us
    • Releated Script: bin/system-bootstrap.sh
    • Responsible for:
      • Setting up CRON jobs
      • Launching Supervisord
      • Capturing the SIGTERM signal so we can gracefully stop Supervisord and run a backup before stopping the entire container
  • ARK SA Bootstrap
    • Related Script: bin/ark-sa-bootstrap.sh
    • Responsible For:
      • Kicking off config file generation if needed
      • Check if the initial game servers files are present then:
        • Launching the Updater or launching the Server
  • ARK SA Updater
    • Related Script: bin/ark-sa-updater.sh
    • Responsible For:
      • Using SteamCMD to download/update the game server files
      • Will launch the game Server once done
  • ARK SA Server
    • Related Script: bin/ark-sa-server.sh
    • Responsible For:
      • Kicking off config file generation if needed
      • Check if the initial game servers files are present then:
        • Prepare all command line args used by the server exe
        • Starts the server using wine/proton
        • tails the ShooterGame.log to the docker log
        • gracefully closes the game/wine/proton when closed
  • ARK SA Backup
    • Related Script: bin/ark-sa-backup.sh
    • Responsible For:
      • Backing up the server's game files
      • Compressed via zip or tar.gz
      • Restarts server or launch updater depending on state
      • Will run when at container stop if configured, before closing Supervisord

Process Flow

If you want (and are familiar with the topic), you can kind of imagine our use of processes as a kind of finite state machine. At any point, only a single core processes is running. Certain events trigger a a flow between states. That is certain conditions causes some processes to start and stop, and we have specific rules that controls the flow.

For instance, a scheduled update could be viewed as a transition of states trigged by a CRON event:

SERVER_RUNNING > SERVER_STOP > UPDATER_START > UPDATER_STOPS > SERVER_RUNNING

Below is a diagram that shows the general flow between the processes and the events that trigger the flow. Pay attention to arrows as they show the direction of the flow. Events with double arrows show a flow in both directions. For instance, on a scheduled backup: the server stops, launches the backup process, then launches the server again ( process A launches process B then stops, B runs then launches A and stops).

image