Aller au contenu
Cloud medium

feint: real machines behind the API, and SSH on all three clouds

2 min de lecture

Cette page existe aussi en français

Give feint a runtime and a server created through the API becomes a real machine, on a real bridge, carrying the address the API published, and one you open an SSH session into. That is what separates an emulator from a mock server: the block a client asks for is the block it gets, a security group really closes a port, and the machine answers. This page compares the four modes, gives the Incus and OVN on Debian 13 procedure, and walks the SSH path on all three providers.

What you will learn

  • Choose the machine mode according to what you want to prove.
  • Install Incus and OVN on Debian 13, OVN wiring included.
  • Verify that the machine carries the address the API published.
  • Open an SSH session on emulated Scaleway, Outscale and Exoscale.

Real machines, on request

By default, feint is a control plane: it answers, and nothing runs. Give it a runtime and a created server becomes a real container or virtual machine, on a real network bridge, carrying the address the API published.

The four modes do not deliver the same thing, and one difference really counts:

--vmMachinesAddressesFirewallCross-VPC isolationOwn kernelRequires
off (default)nonononononothing
incusyesyesyesnonoIncus 6.0.4+
incus-vmyesyesyesnoyesIncus + KVM
incus-ovnyesyesyesyesnoIncus + OVN

Isolation is the only capability whose verdict changes with the mode. Two private networks of two different VPCs must not reach each other. In bridge mode they do, and the runtime documents the cause itself: traffic between managed bridges on one host is routed directly. An OVN network is a logical network with its own router, so the separation comes from topology rather than from a rule.

Rather than guessing, ask the emulator what its mode can prove:

Fenêtre de terminal
feint start --vm incus
curl -s localhost:4599/_feint/health | jq -c '.capabilities'
{"machines":true,"addresses":true,"firewall":true,"isolation":false,"own_kernel":false,"private_from_host":true}

Installing Incus and OVN on Debian 13

Debian 13 is the simplest distribution for this mode, and the procedure has three parts: the repository, the packages, the OVN wiring. The last one is what nobody guesses, and without it incus network create --type=ovn fails with an error naming neither the cause nor the missing setting.

A word on the repository choice, because it is arguable. Debian 13 "trixie" packages Incus 6.0.4, exactly the floor below which network ACLs on an interface are refused. The distribution package is therefore enough, unlike Ubuntu 24.04, which ships 6.0.0 and will not move. The Zabbly repository, published by the Incus maintainer, provides the recommended 7.2 series, the one the project's measurements were taken on.

  1. Install the prerequisites and fetch the repository key into its own keyring. A key placed in /etc/apt/keyrings signs only the repository naming it, where the system trust store would vouch for everything.

    Fenêtre de terminal
    apt-get update
    apt-get install -y --no-install-recommends ca-certificates curl gpg uidmap
    mkdir -p /etc/apt/keyrings
    curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc
    gpg --show-keys --with-fingerprint /etc/apt/keyrings/zabbly.asc

    Check the fingerprint before going further: it must be 4EFC 5906 96CB 15B8 7C73 A3AD 82CC 8797 C838 DCFD, for Zabbly Kernel Builds. That is the only moment you decide whom to trust.

  2. Declare the repository, deriving the suite and architecture from the machine rather than hardcoding them.

    Fenêtre de terminal
    cat > /etc/apt/sources.list.d/zabbly-incus.sources <<EOF
    Enabled: yes
    Types: deb
    URIs: https://pkgs.zabbly.com/incus/stable
    Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
    Components: main
    Architectures: $(dpkg --print-architecture)
    Signed-By: /etc/apt/keyrings/zabbly.asc
    EOF

    On Debian 13, Suites is trixie. There is no curl | sh here: every file is written to disk, and the key is verified before a single package arrives.

  3. Install Incus, OVN and Open vSwitch. Incus comes from Zabbly, the OVN packages from Debian.

    Fenêtre de terminal
    apt-get update
    apt-get install -y --no-install-recommends \
    incus incus-client ovn-central ovn-host openvswitch-switch

    Immediate check: incus --version must answer 7.2 and ovn-nbctl --version ovn-nbctl 25.03.0 on an up-to-date trixie.

  4. Start the services, then wait. The systemd units return before the OVN databases are listening, and a command issued in that window fails for no apparent reason.

    Fenêtre de terminal
    systemctl enable --now openvswitch-switch ovn-central ovn-host
    while [ ! -S /run/ovn/ovnnb_db.sock ]; do sleep 1; done
  5. Tell Open vSwitch where the southbound database is. This is half of the wiring, the half linking the switch to the OVN control plane.

    Fenêtre de terminal
    ovs-vsctl set open_vswitch . \
    external_ids:ovn-remote=unix:/run/ovn/ovnsb_db.sock \
    external_ids:ovn-encap-type=geneve \
    external_ids:ovn-encap-ip=127.0.0.1
  6. Initialise Incus, then give the uplink bridge the range OVN networks will carve out of it.

    Fenêtre de terminal
    incus admin init --minimal
    incus network set incusbr0 ipv4.address=10.108.0.1/24
    incus network set incusbr0 \
    ipv4.dhcp.ranges=10.108.0.10-10.108.0.99 \
    ipv4.ovn.ranges=10.108.0.100-10.108.0.199

    The address is set in a separate command from the ranges: those are validated against the bridge's current address, and incus admin init --minimal picks one at random. Without ipv4.ovn.ranges, creating an OVN network is refused with a message blaming the network rather than the bridge.

  7. Verify with a real network, not with the list of installed packages.

    Fenêtre de terminal
    incus network create ovntest --type=ovn network=incusbr0
    incus network delete ovntest

    Network ovntest created validates the whole wiring. It is the only check that proves anything: packages installed but wired wrong pass every other one.

Do not disable AppArmor. The temptation is real, because the OVN project's own CI turns AppArmor off before its system tests. That workaround targets binaries built from source, outside the paths packaged profiles cover. A package install ships the profiles it needs: on the test VM, Incus, OVN and Open vSwitch all work with AppArmor active.

The final verdict is not read from apt, but from the declared capability:

Fenêtre de terminal
feint doctor --vm auto
feint start --vm auto
curl -s localhost:4599/_feint/health | jq -c '.machines, .capabilities.isolation'

On a freshly installed Debian 13 VM, doctor reports machine runtime: incus-ovn with isolation true, and the health API confirms "incus-ovn" then true. A false value means OVN is absent, or present but not wired.

Does the machine really carry the published address?

Yes, and three commands verify it. Create a private network on a chosen block, attach a server to it, start it, then ask the runtime, not the emulator, what the machine carries.

Fenêtre de terminal
# what the API publishes
curl -s "http://127.0.0.1:4599/ipam/v1/regions/fr-par/ips" | jq -r '.ips[0].address'
# what the machine carries, seen from the runtime
incus exec "feint-scw-<server-id>" -- ip -4 -o addr show

On my machine, IPAM publishes 10.51.0.2/24 and the container carries 10.51.0.2/24: both addresses match. The same run under --vm incus-ovn gives 10.52.0.2/24 on both sides.

Everything the emulator creates is labelled, and feint clean removes exactly those objects, touching nothing else on your host:

removed 1 machine(s), 1 network(s), 1 rule set(s)

Opening an SSH session on an emulated machine

The public address the API publishes is really routed, and you can SSH into it on all three providers. The previous section covered the private plane, the subnet address; this one covers the public plane, the one you log in through. This is where emulation stops being a matter of JSON responses: you register a key through the provider's API, create a server, and open a session with the ordinary SSH client, exactly as on the real cloud.

SSH session open on a machine created through the emulated Scaleway API, at the published address 203.0.113.2

Each provider publishes its addresses in its own documentation block, and that detail has a precise reason: two emulated clouds on the same machine must never route the same /32.

ProviderPublished blockStandardDefault user
Scaleway203.0.113.0/24TEST-NET-3root
Outscale198.51.100.0/24TEST-NET-2outscale
Exoscale192.0.2.0/24TEST-NET-1whatever the template declares

These ranges are reserved for documentation by RFC 5737: they are routable nowhere on the Internet, which makes them exactly right for a public address that exists only on your machine. The path below needs an active machine runtime, so --vm incus at minimum.

Fenêtre de terminal
ssh-keygen -t ed25519 -N '' -C feint-lab -f ./id
eval "$(feint env scaleway)"
# the key goes through the IAM API, as on the real Scaleway
scw iam ssh-key create name=feint-lab public-key="$(cat ./id.pub)"
# a flexible IP, then the server carrying it
ip_id=$(scw instance ip create zone=fr-par-1 -o json | jq -r '(.ip // .).id')
srv=$(scw instance server create name=ssh-lab type=DEV1-S zone=fr-par-1 ip="$ip_id" -o json | jq -r .id)
scw instance server start "$srv" zone=fr-par-1
# the address the API publishes is the one you log in to
ip=$(scw instance server get "$srv" zone=fr-par-1 -o json | jq -r '.public_ips[0].address')
ssh -F /dev/null -i ./id -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no "root@$ip"

Inside the machine, the interface carries the published address as a /32, beside the network's private address:

ssh-lab
root
eth0 203.0.113.2/32
eth0 10.209.84.9/24

What the host can reach depends on the mode

A private subnet address answers from the host in bridge mode, but not under OVN, and the emulator declares it rather than leaving you to guess. The cause is topological: the logical router separating two VPCs also translates connections coming from the host on the way back.

Under --vm incus-ovn, the health call returns "isolation":true and "private_from_host":false. The two capabilities therefore move in opposite directions, and that is the trade-off to know: the mode that truly isolates two VPCs is the one from which your workstation cannot reach an internal address. The public plane stays reachable in both modes, which is precisely why the SSH paths above go through a public address.

Key points

  • By default feint runs no machine: off is a control plane, and that is what CI runs.
  • Only --vm incus-ovn isolates two VPCs, at the cost of a private address the host can no longer reach.
  • The machine carries the published address, verifiable from the runtime rather than from the emulator.
  • An SSH session opens on all three providers, each on its own documentation address block.
  • feint clean removes only what the emulator created, the rest of the host is never touched.

Next steps