engine

package
v0.7.14 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: AGPL-3.0 Imports: 57 Imported by: 0

Documentation

Overview

Package engine installs and talks to Zelie's own containerd.

Index

Constants

View Source
const (
	ContainerdVersion = "2.4.1"
	RuncVersion       = "1.5.1"
	CNIVersion        = "1.9.1"
	RailpackVersion   = "0.40.0"
)
View Source
const BuilderHostID = builderBase

BuilderHostID is the host user and group that root inside the builder is. Directories the builder writes to must belong to it.

View Source
const LocalImages = "zelie.local/"

LocalImages is the prefix of images built on this machine. They are never pulled from a registry.

View Source
const MaxFileSize = 512 << 10

MaxFileSize is the largest File the engine writes.

View Source
const MaxOpenFiles = 1 << 20

MaxOpenFiles is the most a container may ask for as its limit on open files, and what game servers ask for. containerd's own spec leaves containers at 1024, which Wine's file-descriptor based synchronisation and big Unity or Unreal servers run out of. Docker's default is this number too.

View Source
const Namespace = "zelie"

Namespace is the containerd namespace that holds everything Zelie creates.

Variables

View Source
var DefaultPaths = Paths{
	Bin:    "/usr/local/lib/zelie/bin",
	CNI:    "/usr/local/lib/zelie/cni",
	Config: "/etc/zelie/containerd.toml",
	Root:   "/var/lib/zelie/containerd",
	State:  "/run/zelie/containerd",
	Socket: "/run/zelie/containerd.sock",
	Unit:   "/etc/systemd/system/zelie-containerd.service",
	Logs:   "/var/lib/zelie/logs",
	Data:   "/var/lib/zelie/engine",
	NetNS:  "/run/zelie/netns",

	Volumes: "/var/lib/zelie/volumes",
}
View Source
var ErrInputClosed = errors.New("the container is not reading its input")

ErrInputClosed means the container is not reading its standard input.

View Source
var NetworkRange = netip.MustParsePrefix("10.210.0.0/16")

NetworkRange holds every container network. Each network gets a /24 out of it. It is rarely used by hosting providers or home routers, which keeps clashes with the host's own networks unlikely.

Functions

func CheckForwards added in v0.6.0

func CheckForwards(app string, forwards []Forward) error

CheckForwards reports whether forwards may be set on app.

func CheckLinks(app string, links []Link) error

CheckLinks reports whether links may be set on app.

func CheckVolumeTarget

func CheckVolumeTarget(target string) error

CheckVolumeTarget reports whether a volume may be mounted at target.

func ConfigFile

func ConfigFile(p Paths) string

ConfigFile renders containerd's configuration. The CRI plugins are turned off because Zelie is not a Kubernetes node and they would only add attack surface and memory use.

func ImageName

func ImageName(ref string) (string, error)

ImageName is the name an image is stored under: "nginx:alpine" means docker.io/library/nginx:alpine, as it does everywhere else. Local images keep their name.

func LogPathFor

func LogPathFor(p Paths, id string) string

LogPathFor is where a container's output goes. The shim writes to it directly, so logging keeps working while containerd or Zelie restarts.

func ParseSignal added in v0.6.0

func ParseSignal(name string) (syscall.Signal, bool)

ParseSignal turns a name such as SIGINT into a signal a container may be sent.

func Pinned added in v0.5.0

func Pinned(ref string) bool

Pinned reports whether ref names one exact image, by digest, rather than whatever its tag points at.

func RefreshUnit added in v0.7.7

func RefreshUnit(ctx context.Context, p Paths, systemctl func(ctx context.Context, args ...string) error) (bool, error)

RefreshUnit rewrites containerd's unit and config when this binary renders them differently, so changes reach servers that were installed by an older version. Updates swap the binary but never run the installer. Nothing happens when the unit does not exist: the engine was never installed here.

The restart is safe for running containers, see UnitFile. It uses --no-block because zelie-core.service requires containerd: restarting containerd restarts the core too, and waiting for that job from inside the core would wait on our own stop. The core comes back, finds the files unchanged and carries on.

A marker file stands for the restart until it is queued. Without it, a start cut short after the files were written would find them in place next time and leave containerd on the old unit.

func UnitFile

func UnitFile(p Paths) string

UnitFile renders the systemd unit. KillMode=process matters most: when containerd stops or restarts, systemd kills only containerd itself and leaves the shims, and therefore every running container, alone. LimitNOFILE is the ceiling for what containers can ask for: see Engine.openFiles. containerd looks for unpigz and igzip once and keeps using the path it found, so a package manager removing one, as happens when Docker is uninstalled, broke every pull until a restart; Go's own gzip is used instead.

func ValidID

func ValidID(id string) bool

ValidID reports whether id is safe to use as a container name and in paths.

Types

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine runs containers through Zelie's containerd.

func Connect

func Connect(ctx context.Context, p Paths) (*Engine, error)

func (*Engine) Close

func (e *Engine) Close() error

func (*Engine) CreateVolume

func (e *Engine) CreateVolume(name string) error

CreateVolume makes an empty volume. Its top directory belongs to root in the container, which is who images expect to own a fresh mount.

func (*Engine) Exec

func (e *Engine) Exec(ctx context.Context, id string, args []string, stdin io.Reader, stdout, stderr io.Writer) (uint32, error)

Exec runs a command in a running container and returns its exit code. It runs as root inside the container, with the container's environment, so a database's own tools can read the password it was started with.

When stdin is set the command reads it to the end. The last bytes of stdin can be lost on the way through the shim, so a caller that must know all of it arrived checks that for itself.

func (*Engine) ForwardedPorts added in v0.7.4

func (e *Engine) ForwardedPorts() ([]uint16, error)

ForwardedPorts lists the host ports any app forwards, whatever the protocol or address.

func (*Engine) Images

func (e *Engine) Images(ctx context.Context) ([]Image, error)

Images lists the images the engine holds.

func (*Engine) ImportImage

func (e *Engine) ImportImage(ctx context.Context, r io.Reader, name string) error

ImportImage stores an OCI image archive, such as a build's output, under name, which must start with LocalImages.

func (e *Engine) Links(app string) ([]Link, error)

Links returns the links of an app.

func (*Engine) List

func (e *Engine) List(ctx context.Context) ([]Status, error)

func (*Engine) OpenVolume

func (e *Engine) OpenVolume(ctx context.Context, name string) (*os.Root, error)

OpenVolume opens a volume for the core to change its files directly, as a restore does. It is refused while a container using the volume runs. The files came from a container and are not trusted: the root keeps every path, symbolic links included, inside the volume.

func (*Engine) Pin added in v0.5.0

func (e *Engine) Pin(ctx context.Context, ref string) (string, error)

Pin returns ref with the digest of the image it names here, such as postgres:18@sha256:…, and keeps the image under that name too. A tag moves when a new build is published; the pinned name lets an app be started again, or rolled back, on exactly what it ran before. Local images and refs that are already pinned come back as they are.

func (*Engine) ReadVolume

func (e *Engine) ReadVolume(name string) (*os.Root, error)

ReadVolume opens a volume while its containers may still run, for a backup that does not stop the app and for the file manager. Unlike OpenVolume it does not look at the containers, so whoever uses it must be fine with files that change underneath.

func (*Engine) Remove

func (e *Engine) Remove(ctx context.Context, id string) error

Remove stops the container if needed and deletes it with its snapshot and its log. Once started it runs to the end even if ctx is cancelled: a network plugin killed halfway leaves the container's address taken.

func (*Engine) RemoveImage

func (e *Engine) RemoveImage(ctx context.Context, name string) error

RemoveImage deletes an image. Containers already made from it keep running: they hold their own snapshot. containerd's garbage collector frees the layers no other image uses.

func (*Engine) RemoveVolume

func (e *Engine) RemoveVolume(ctx context.Context, name string) error

RemoveVolume deletes a volume and everything in it. A volume a container still has mounted is refused, stopped or not.

func (*Engine) Run

func (e *Engine) Run(ctx context.Context, s Spec) (err error)

Run pulls the image if needed, creates the container and starts it.

func (*Engine) SetForwards added in v0.6.0

func (e *Engine) SetForwards(ctx context.Context, app string, forwards []Forward) error

SetForwards replaces the forwards of an app; none clears them. They are saved, so they come back when the core restarts, and follow the app's container to its new address whenever it has one.

func (e *Engine) SetLinks(ctx context.Context, app string, links []Link) error

SetLinks replaces the links of an app. They apply at once, to containers already running too.

func (*Engine) SetUnused

func (e *Engine) SetUnused(ctx context.Context, name string, since time.Time) error

SetUnused records since when an image has been unneeded. The zero time clears it.

func (*Engine) Signal added in v0.6.0

func (e *Engine) Signal(ctx context.Context, id string, sig syscall.Signal) error

Signal sends a signal to the container's main process.

func (*Engine) StartDNS

func (e *Engine) StartDNS(ctx context.Context) (<-chan error, error)

StartDNS starts the DNS server of every network; they run until ctx is cancelled. Networks made later get theirs as their first container starts. A server that fails later reports it on the channel.

func (*Engine) Stop

func (e *Engine) Stop(ctx context.Context, id string, grace time.Duration) error

Stop asks the container to exit and kills it if it has not exited within the grace period.

func (*Engine) Usage

func (e *Engine) Usage(id string) (Usage, error)

Usage reads a container's memory and CPU time from its cgroup.

func (*Engine) VolumeSize

func (e *Engine) VolumeSize(name string) (int64, error)

VolumeSize returns how much disk one volume takes.

func (*Engine) VolumeSizes

func (e *Engine) VolumeSizes() (map[string]int64, error)

VolumeSizes returns how much disk each volume takes, by name.

func (*Engine) Wait

func (e *Engine) Wait(ctx context.Context, id string) (uint32, error)

Wait waits until the container's process has exited and returns its exit code.

func (*Engine) WriteStdin added in v0.6.0

func (e *Engine) WriteStdin(ctx context.Context, id string, data []byte) error

WriteStdin writes to the standard input of a container that was created with Spec.Stdin.

type File added in v0.6.0

type File struct {
	Target  string
	Content []byte
}

File is a file to put in a container. It belongs to root on the host and is readable by everyone, so the container's own root can read it.

type Forward added in v0.6.0

type Forward struct {
	// IP is the host address the port is open on. The zero address, or
	// 0.0.0.0, means all of them.
	IP     netip.Addr `json:"ip,omitzero"`
	Port   uint16     `json:"port"`
	Proto  string     `json:"proto"` // tcp or udp
	Target uint16     `json:"target"`
}

A forward opens a port of the host to a port of an app's container, for players who are not on Zelie's networks: game servers, which cannot go through the web proxy. Nothing else of the container is reachable this way.

type Host

type Host struct {
	CPUs          int
	MemoryBytes   int64
	DiskBytes     int64 // of the file system that holds Zelie's data
	DiskFreeBytes int64
}

Host is what the server has to give.

func HostInfo

func HostInfo(dir string) (Host, error)

HostInfo describes the server. dir is a directory on the disk that holds the containers.

type IDs added in v0.6.0

type IDs struct{ UID, GID uint32 }

IDs is a user and group inside a container.

func (IDs) Valid added in v0.6.0

func (i IDs) Valid() bool

Valid reports whether both are IDs the container has. It has one block of them; anything beyond is nobody.

type Image

type Image struct {
	Name string
	// UnusedSince is when the image was first found unneeded; zero while
	// something needs it.
	UnusedSince time.Time
}

Image is an image the engine holds.

type Installer

type Installer struct {
	Paths  Paths
	Arch   string
	Client *http.Client
	Log    io.Writer
	// contains filtered or unexported fields
}

Installer puts Zelie's containerd and runc on the machine and keeps the systemd service running. Running it again is safe: files that are already correct are left alone, and containerd is only restarted when something changed.

func (*Installer) Install

func (in *Installer) Install(ctx context.Context) error
type Link struct {
	Name string `json:"name"` // what the app looks up, such as "db"
	To   string `json:"to"`   // the app it reaches
	Port uint16 `json:"port"`
}

Apps cannot reach each other: each has its own network, and the host firewall drops traffic between networks. A link opens one way through: the app's containers may connect to one TCP port of another app's containers, and find them by the link's name through the DNS server.

type Mount

type Mount struct {
	Source, Target string
	ReadOnly       bool
}

Mount binds a host directory into a container.

type Paths

type Paths struct {
	Bin    string // containerd, the shim, ctr and runc
	CNI    string // network plugins
	Config string
	Root   string // images, snapshots, container metadata
	State  string // runtime state, cleared on reboot
	Socket string
	Unit   string // systemd unit file
	Logs   string // container output, one file per container
	Data   string // Zelie's own state: networks, IP leases, generated files
	NetNS  string // network namespaces, one per container
	// Volumes holds the files apps keep between deployments, one
	// directory each.
	Volumes string
}

Paths says where Zelie's containerd lives. None of them overlap with a containerd installed by Docker or the distribution, so both can run on the same machine without touching each other.

func (Paths) Railpack

func (p Paths) Railpack() string

func (Paths) Runc

func (p Paths) Runc() string

type PortForwardedError added in v0.6.0

type PortForwardedError struct {
	Port  uint16
	Proto string
}

PortForwardedError means another app already forwards the port.

func (*PortForwardedError) Error added in v0.6.0

func (e *PortForwardedError) Error() string

type Spec

type Spec struct {
	ID    string
	App   string // the app the container belongs to, if any
	Image string
	Args  []string // replaces the image's command when set
	Env   []string

	// Network is the network to join. Containers on the same network can
	// reach each other. Without one the container has only loopback.
	Network string

	MemoryBytes int64   // required
	CPUs        float64 // required, may be fractional
	Pids        int64   // required

	// Volumes are the app's own files that outlive the container.
	Volumes []VolumeMount

	// Mounts are host directories bound into the container. They are for
	// the core's own use, such as a build's source and cache, and are not
	// part of the core's API.
	Mounts []Mount

	// Files are written by the core and bound read-only into the container,
	// such as an install script. They live in the container's own
	// directory and go when it does. Like Mounts, only the core sets them.
	Files []File

	// User is the user and group the process runs as inside the container,
	// in place of the image's own. Game servers run as an ordinary user, the
	// way Pterodactyl's images expect.
	User *IDs
	// WorkDir replaces the image's working directory.
	WorkDir string
	// OpenFiles is the limit on open files, soft and hard, up to
	// MaxOpenFiles. Zero leaves containerd's default of 1024. It is
	// lowered to what containerd itself may give.
	OpenFiles uint64
	// Stdin keeps the container's standard input open for WriteStdin. It
	// stays open until the container is stopped or removed.
	Stdin bool

	// Builder runs the container as the builder: in the builder's own ID
	// block, so what it writes stays readable by the next build step. Only
	// one builder container exists at a time. Like Mounts, only the core
	// sets it.
	Builder bool
	// Nesting lets a builder run containers of its own: see nestingOpts.
	Nesting bool
}

Spec describes a container to run.

func (Spec) Validate

func (s Spec) Validate() error

Validate checks the spec before anything is created.

type Status

type Status struct {
	ID      string
	App     string
	Image   string
	State   string // running, stopped, created, …
	Pid     uint32
	Userns  uint32 // first host ID of the container's user namespace
	Network string
	IP      netip.Addr // zero when the container has no network
}

Status is a short summary of a container.

type Usage

type Usage struct {
	MemoryBytes int64
	// CPUUsec is the CPU time used so far; two readings give the rate.
	CPUUsec int64
	// Bytes received and sent on the container's network so far.
	RxBytes, TxBytes int64
}

Usage is what a running container uses.

type VolumeMount

type VolumeMount struct {
	Name, Target string
}

VolumeMount puts a volume at Target inside the container.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL