UDP servers and [services.concurrency]

Can I set services.concurrency for UDP servers? For TCP servers it refers to concurrent connections, but is it interpreted as e.g. packets/s for UDP servers?

Given a UDP server might have a lot of UDP traffic and relatively little TCP traffic (e.g., DNS), I’d like to set a scaling threshold based purely/primarily on the UDP traffic.

This is something we haven’t quite figured out how to expose yet. How would you want to scale instances doing UDP? Would you want to use some packets per second metric, or prefer something like system load?

Packets per second would be great. Ideally that would also be configurable to include a time period (e.g., > 500 queries/s over 30 seconds) to ignore short bursts.

Nice. The plan is to enable scaling on any metric we’re tracking, so once we get that going you should be set.

We do a lot of work to enforce connection limits on VMs through our load balancers, but UDP bypasses all that. We haven’t decided what to do about that yet.

2 Likes

Has there been any update on UDP metrics? I notice they’re not available for the edge (or even instance, although I think they’re tracked in instance data throughput).

Additionally, is UDP traffic charged?

@kurt is scaling based on UDP traffic still on the roadmap? I’ve been getting my feet wet on Fly by running a Factorio server on Fly. I’d love to be able to scale down to 0 when no one is using it. But I can’t currently find a way to autoscale based on the UDP traffic.

Assuming you haven’t considered Machines… scaling down to zero is the kind of use case they were built for: Advantages/Disadvantages of Machine Apps - #2 by fideloper-fly

You’d have to create a new Fly app, as existing apps can’t simply be migrated over to Machines.

1 Like

Thanks for the recommendation! I got it partially doing what I want on the Machines service. I’ve got it scaling down to zero when all users leave. But it’s not waking up when a user tries to join. I’m not super well versed in the factorio netcode, but I think it’s just sending a UDP packet to the server to signal that it wants to connect. It seems that lone packet isn’t waking up my machine though. Any ideas on what I should look for to get the machine to turn back on at the right time?

I haven’t used UDP with Machines myself, but previously, Thomas (eng at Fly) did say that UDP should wake Machines up: Machines first impressions - #3 by thomas … If it isn’t, it is likely a bug.

Btw, raw TCP and UDP don’t work if you don’t allocate a dedicated IPv4 (costs $2/mo) to your Fly app: TCP and UDP service ports don't work - #2 by ignoramous (today, UDP on Fly works over dedicated IPv4 only, anyway)

1 Like

It’s very likely I just have something configured incorrectly. I do have a dedicated ipv4 provisioned. I can connect to the game server and it works great. Scales down when everyone leaves as desired. I just can’t get it to wake back up without manually issuing a command through flyctl or the machines HTTP API.

1 Like

having the same issue with a factorio server @willcosgrove did you ever get this working?

I did not :disappointed: My final solution ended up being to write a very small HTTP service that worked as a Discord bot. The bot would listen for wake commands in a discord room, and then issue a Fly API call to wake up the machine.

@willcosgrove I’m interested in trying this, haven’t had luck getting a machine up and running with the latest headless 1.100 though. Would you mind sharing you config?

I’ll give you what I can. My repo is not very clean or organized and it has a bunch of environment secrets in there, so I can’t make it public. But I can share the main parts of my Dockerfile and fly.toml.

I haven’t included anything about the discord bot I mentioned in the last post, as I couldn’t remember how it’s even working. Best of luck!

Dockerfile

FROM factoriotools/factorio:stable

COPY config/server-settings.json /server-settings.json
COPY mods/mod-list.json /mod-list.json
COPY saves/anustart.zip /anustart.zip
COPY --chmod=0755 docker-entrypoint.sh /docker-entrypoint.sh
COPY linux_factorio_watcher /linux_factorio_watcher

ENV INSTANCE_NAME="Will's Machine"
ENV INSTANCE_DESCRIPTION="A really good machine"
ENV BIND="fly-global-services"
ENV RCON_BIND="127.0.0.1:27015"
ENV RCON_PASSWORD="ok"
ENV SAVE_NAME="anustart"
ENV TOKEN="945790cb742ffcc72d23a0173a0995"
ENV WEBHOOK_URL="https://discord.com/api/webhooks/1059567465396457472/K2hA-SUsZeoo1sXe_ws_BHYBfGvYftPVIzFJ5SfPkMZRi7zumAhrdHiND3_stFp6Rsdm"
ENV USERNAME="willcosgrove"
ENV UPDATE_MODS_ON_START="true"

ENTRYPOINT /bin/sh -c "mkdir -p /factorio/config && envsubst < /server-settings.json > /factorio/config/server-settings.json && mkdir -p /factorio/mods && cp /mod-list.json /factorio/mods/mod-list.json && mkdir -p /factorio/saves && cp -u /save.zip /factorio/saves/save.zip && ./docker-entrypoint.sh"

Docker Entrypoint

#!/bin/bash
set -eoux pipefail

FACTORIO_VOL=/factorio
LOAD_LATEST_SAVE="${LOAD_LATEST_SAVE:-true}"
GENERATE_NEW_SAVE="${GENERATE_NEW_SAVE:-false}"
SAVE_NAME="${SAVE_NAME:-""}"
BIND="${BIND:-""}"

mkdir -p "$FACTORIO_VOL"
mkdir -p "$SAVES"
mkdir -p "$CONFIG"
mkdir -p "$MODS"
mkdir -p "$SCENARIOS"
mkdir -p "$SCRIPTOUTPUT"

if [[ ! -f $CONFIG/rconpw ]]; then
  # Generate a new RCON password if none exists
  pwgen 15 1 >"$CONFIG/rconpw"
fi

if [[ ! -f $CONFIG/server-settings.json ]]; then
  # Copy default settings if server-settings.json doesn't exist
  cp /opt/factorio/data/server-settings.example.json "$CONFIG/server-settings.json"
fi

if [[ ! -f $CONFIG/map-gen-settings.json ]]; then
  cp /opt/factorio/data/map-gen-settings.example.json "$CONFIG/map-gen-settings.json"
fi

if [[ ! -f $CONFIG/map-settings.json ]]; then
  cp /opt/factorio/data/map-settings.example.json "$CONFIG/map-settings.json"
fi

NRTMPSAVES=$( find -L "$SAVES" -iname \*.tmp.zip -mindepth 1 | wc -l )
if [[ $NRTMPSAVES -gt 0 ]]; then
  # Delete incomplete saves (such as after a forced exit)
  rm -f "$SAVES"/*.tmp.zip
fi

if [[ ${UPDATE_MODS_ON_START:-} == "true" ]]; then
  ./docker-update-mods.sh
fi

if [[ $(id -u) = 0 ]]; then
  # Update the User and Group ID based on the PUID/PGID variables
  usermod -o -u "$PUID" factorio
  groupmod -o -g "$PGID" factorio
  # Take ownership of factorio data if running as root
  chown -R factorio:factorio "$FACTORIO_VOL"
  # Drop to the factorio user
  SU_EXEC="su-exec factorio"
else
  SU_EXEC=""
fi

sed -i '/write-data=/c\write-data=\/factorio/' /opt/factorio/config/config.ini

NRSAVES=$(find -L "$SAVES" -iname \*.zip -mindepth 1 | wc -l)
if [[ $GENERATE_NEW_SAVE != true && $NRSAVES ==  0 ]]; then
    GENERATE_NEW_SAVE=true
    SAVE_NAME=_autosave1
fi

if [[ $GENERATE_NEW_SAVE == true ]]; then
    if [[ -z "$SAVE_NAME" ]]; then
        echo "If \$GENERATE_NEW_SAVE is true, you must specify \$SAVE_NAME"
        exit 1
    fi
    if [[ -f "$SAVES/$SAVE_NAME.zip" ]]; then
        echo "Map $SAVES/$SAVE_NAME.zip already exists, skipping map generation"
    else
        $SU_EXEC /opt/factorio/bin/x64/factorio \
            --create "$SAVES/$SAVE_NAME.zip" \
            --map-gen-settings "$CONFIG/map-gen-settings.json" \
            --map-settings "$CONFIG/map-settings.json"
    fi
fi

FLAGS=(\
  --port "$PORT" \
  --server-settings "$CONFIG/server-settings.json" \
  --server-banlist "$CONFIG/server-banlist.json" \
  --rcon-bind "$RCON_BIND" \
  --server-whitelist "$CONFIG/server-whitelist.json" \
  --use-server-whitelist \
  --server-adminlist "$CONFIG/server-adminlist.json" \
  --rcon-password "$RCON_PASSWORD" \
  --server-id /factorio/config/server-id.json \
)

if [ -n "$BIND" ]; then
  FLAGS+=( --bind "$BIND" )
fi

if [[ $LOAD_LATEST_SAVE == true ]]; then
    FLAGS+=( --start-server-load-latest )
else
    FLAGS+=( --start-server "$SAVE_NAME" )
fi

# shellcheck disable=SC2086
exec $SU_EXEC /opt/factorio/bin/x64/factorio "${FLAGS[@]}" "$@"

Fly.toml

# fly.toml file generated for will-factorio-machine on 2022-12-31T16:45:47-06:00

app = "will-factorio-machine"
kill_signal = "SIGINT"
kill_timeout = 5
processes = []

[env]

[experimental]
  allowed_public_ports = []
  auto_rollback = true

[[services]]
  internal_port = 34197
  protocol = "udp"
  [[services.ports]]
    port = 34197

[[services]]
  internal_port = 27015
  protocol = "tcp"
  [[services.ports]]
    port = 27015

Whoops, an erroneous click led me to delete my post by accident. Re-posting:

Thanks willcosgrove , got mine up and running :slight_smile:

No luck with the autoscaling either though, with this config the server auto-shuts off even if clients are connected:

[[services]]
  protocol = "udp"
  internal_port = 34197
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0

  [[services.ports]]
    port = 34197

[services.concurrency]
  type = "connections"
  hard_limit = 3
  soft_limit = 1