Skip to content

oshlabs/tank

Repository files navigation

Tank

CI

A declarative container orchestrator for Linux, built on Linx.

You describe the pods that should run — their image, network, resources, and restart policy — as plain Elixir data. Tank persists that desired state in an embedded Khepri store, and a reconcile loop converges the machine toward it, keeping it there across drift, crashes, and reboots. It is the Kubernetes shape collapsed to a single node: you never imperatively start a container — you state intent with Tank.apply/1, and the loop makes reality match.

Tank is a consumer of Linx, not part of it. The cross-subsystem "container" object — spawn into namespaces, reconcile the network from the host, supervise the whole thing — is composed entirely from Linx's public primitives (Linx.Process, Linx.Netlink.Rtnl) plus OTP supervision and a Khepri store. By design that composite can't live in a primitives library, so it lives here.

⚠️ Early-stage (0.x). Tank is a working proof of concept maturing into a real orchestrator; the API may change between minor releases until 1.0.

Installation

def deps do
  [
    {:tank, "~> 0.1"}
  ]
end

Requirements

  • Linux. Tank drives kernel namespaces, mounts, and network interfaces, so it runs only on Linux and needs privileges to configure them (root, or the appropriate capabilities).
  • Erlang/OTP 28 or earlier. Tank's store uses Khepri, whose Horus dependency cannot yet extract stored functions under OTP 29. Run on OTP 28 until that support lands upstream.

A taste

# State intent — the reconciler brings the pod up and keeps it up.
Tank.apply(%{
  name: "web",
  restart: :always,
  network: %{
    nics: [%{name: "eth0", parent: "eth0", ip: {"10.0.0.5", 24}, gateway: "10.0.0.1"}],
    dns: ["10.0.0.1"]
  },
  containers: [%{name: "app", image: "nginx:1.27"}]
})

Tank.list()        #=> [%Tank.Pod{name: "web", ...}]
Tank.exec("web", ["/bin/sh"])   # a shell beside the running container
Tank.delete("web") #=> :ok   (the reconciler tears it down)

A pod is one network namespace holding one or more containers; apply/1 is create-or-replace and validates the map into a %Tank.Pod{} up front. The full surface — images and the OCI command/env merge, volumes and mounts, cgroup limits, macvlan networking, the reconcile loop, boot seeding, and interactive exec/attach — is in Tank by example.

What it composes

For each pod, on every (re)start:

  1. Linx.Process spawns the workload into fresh namespaces and parks it at the :ready checkpoint.
  2. Linx.Netlink.Rtnl + Rtnl.Reconcile configure that namespace from the host while the workload waits — raise interfaces, converge the desired addresses and routes — then the workload proceeds.
  3. OTP supervision + the reconciler restart the composite on an abnormal exit, with a brand-new namespace reconfigured from scratch (lifetime = ownership: the network dies and is reborn with the container).

Running the tests

The reconcile path needs real namespaces, so the integration tests need root:

mix deps.get
sudo ./sudotest.sh

The non-privileged suite runs under a plain mix test.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors