Nebula
Orchestrator

HetznerWorkerHost

One Hetzner Cloud VM per worker, created through the Hetzner Cloud API (https://docs.hetzner.cloud).

generated from the sources
public sealed class HetznerWorkerHost : IWorkerHost

One Hetzner Cloud VM per worker, created through the Hetzner Cloud API (https://docs.hetzner.cloud). Each VM boots Ubuntu with a cloud-init script that downloads the Linux server build from the orchestrator over the private network, reads its own private IP from the Hetzner metadata service, and starts the worker role as a systemd unit advertising that private IP. Killing a worker deletes its VM. On Initialize every VM labelled with this mesh id is deleted, so a restarted orchestrator never inherits (and pays for) stale machines.

All HTTP work runs on the thread pool; results are queued and applied in Tick on the main thread, where the JSON is parsed with JsonUtility.

Constructors

HetznerWorkerHost

public HetznerWorkerHost(CloudHostSettings settings);

Fields

BillingPeriodSeconds

public const float BillingPeriodSeconds = 3600f;

Hetzner charges per started hour, so a machine deleted after 61 minutes has cost two.

BillingSafetyMarginSeconds

public const float BillingSafetyMarginSeconds = 120f;

Delete a parked machine this long before the paid hour ends, so the delete call lands inside it.

WorkerEnvFile

public const string WorkerEnvFile = "/etc/nebula/worker.env";

The worker's environment file on its VM: the game's variables, applied by systemd before the worker starts.

Properties

Name

public string Name { get; }

SupportsParking

public bool SupportsParking { get; }

A VM already paid for is worth keeping: a retired worker is parked and unparked instead of deleted and recreated.

TypicalBootSeconds

public float TypicalBootSeconds { get; }

Measured: create, Ubuntu boot, build download and Unity start is well over a minute.

IsReady

public bool IsReady { get; }

InitializationError

public string InitializationError { get; } = "";

Methods

Initialize

public void Initialize(Action<string, string> log);

Launch

public IWorkerHandle Launch(WorkerLaunchSpec spec);

Kill

public void Kill(IWorkerHandle handle);

BilledUntilSeconds

public static float BilledUntilSeconds(float launchedAt, float now, float billingPeriodSeconds = BillingPeriodSeconds);

End of the billing period a machine launched at launchedAt has been charged for as of now. Hetzner bills per started hour, so a machine up for five minutes is paid for until minute 60 and one up for 61 minutes until minute 120; keeping it parked until then is free. Pure function.

RetainUntilSeconds

public static float RetainUntilSeconds(float launchedAt, float now, float idlePoolSeconds);

When a machine parked at now stops being kept: the end of the hour it has already been billed, or sooner when idlePoolSeconds asks for less. A longer idle pool is not honoured, because the hour after this one is a fresh charge and nobody asked to pay it.

BillingSafetyMarginSeconds is taken off the billed deadline only, so the delete call lands before the next hour starts. It is not taken off a retention the developer asked for: an IdlePoolSeconds under the margin would otherwise mean "delete on the next tick".

Pure function; the result is the wall-clock moment the machine goes.

ParkedInstanceExpired

public static bool ParkedInstanceExpired(float now, float retainUntil);

True when a parked machine kept until retainUntil must be deleted now. Pure function.

Park

public void Park(IWorkerHandle handle, float idlePoolSeconds);

Keep the machine instead of deleting it: the worker process stays up and keeps heartbeating, the orchestrator simply deals it nothing. It is deleted in Tick once the hour Hetzner has already charged for runs out (or sooner, when idlePoolSeconds is shorter), unless Unpark claims it first.

Unpark

public bool Unpark(IWorkerHandle handle);

Take a parked machine back into service. The worker never stopped, so it is ready at once.

Tick

public void Tick();

WriteHandleJson

public void WriteHandleJson(IWorkerHandle handle, JsonWriter w);

Dispose

public void Dispose();

BuildCloudInit

public static string BuildCloudInit(string buildUrl, string workerArgs, System.Collections.Generic.IDictionary<string, string> env = null, Action<string> warn = null);

Boot script for a worker VM. Waits for the private interface, fetches the build from the orchestrator, writes the game's variables (env) to WorkerEnvFile (root-only), and runs the worker as a systemd unit that reads it. $PRIVATE_IP in workerArgs expands on the machine; the variables are written literally.

SystemdEnvLine

public static string SystemdEnvLine(string key, string value);

One line of a systemd environment file: KEY="value" with \, ", $ and ` escaped. Null when the value has a line break, which the format cannot carry safely.

On this page