feat: run smolmachines sidecar as vm
This commit is contained in:
@@ -17,7 +17,6 @@ from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import os
|
||||
import platform
|
||||
from contextlib import ExitStack, contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Callable, Generator
|
||||
@@ -53,6 +52,7 @@ from ...bottle_state import (
|
||||
read_committed_image,
|
||||
)
|
||||
from . import loopback_alias as _loopback
|
||||
from . import port_forward as _forward
|
||||
from . import sidecar_bundle as _bundle
|
||||
from . import smolvm as _smolvm
|
||||
from .bottle import SmolmachinesBottle
|
||||
@@ -94,7 +94,6 @@ def launch(
|
||||
plan = _mint_certs(plan)
|
||||
proxy_host = _proxy_host(plan, loopback_ip)
|
||||
plan = _start_bundle(plan, network, proxy_host, stack)
|
||||
plan = _discover_urls(plan, proxy_host)
|
||||
|
||||
agent_from_path = _agent_from_path(plan)
|
||||
|
||||
@@ -144,19 +143,17 @@ def _allocate_resources(
|
||||
plan: SmolmachinesBottlePlan,
|
||||
stack: ExitStack,
|
||||
) -> tuple[str, str]:
|
||||
"""Reserve a loopback alias and create the per-bottle docker bridge.
|
||||
"""Reserve a per-bottle host address.
|
||||
|
||||
The per-bottle alias scopes TSI's allowlist to this bottle's
|
||||
published ports so the agent can't reach other bottles' or host
|
||||
services' ports on loopback. On macOS `ensure_pool` first
|
||||
sudo-aliases the pool on `lo0`; on Linux that's a no-op since
|
||||
all of 127.0.0.0/8 is already loopback, but the per-bottle
|
||||
allocation runs on both."""
|
||||
The per-bottle address scopes TSI's allowlist to this bottle's
|
||||
forwarder-published ports so the agent can't reach other bottles'
|
||||
or host services. The returned network name remains in the bundle
|
||||
spec for compatibility with older helper tests; no Docker sidecar
|
||||
container is launched."""
|
||||
del stack
|
||||
_loopback.ensure_pool()
|
||||
loopback_ip = _loopback.allocate(plan.slug)
|
||||
network = _bundle.bundle_network_name(plan.slug)
|
||||
_bundle.create_bundle_network(network, plan.bundle_subnet, plan.bundle_gateway)
|
||||
stack.callback(_bundle.remove_bundle_network, network)
|
||||
return loopback_ip, network
|
||||
|
||||
|
||||
@@ -179,15 +176,40 @@ def _start_bundle(
|
||||
proxy_host: str,
|
||||
stack: ExitStack,
|
||||
) -> SmolmachinesBottlePlan:
|
||||
"""Build the BundleLaunchSpec, resolve token env, start the
|
||||
sidecar bundle container, and register teardown."""
|
||||
"""Build the BundleLaunchSpec, start the sidecar VM, wrap its raw
|
||||
smolVM-published loopback ports with per-bottle forwarders, stamp
|
||||
agent URLs from those forwarder ports, and register teardown."""
|
||||
plan = _provision_git_gate_keys(plan)
|
||||
bundle_spec = _bundle_launch_spec(plan, network, proxy_host)
|
||||
token_env = _resolve_token_env(plan, dict(os.environ))
|
||||
_bundle.ensure_bundle_image(bundle_spec.image)
|
||||
_bundle.start_bundle(bundle_spec, env={**os.environ, **token_env})
|
||||
stack.callback(_bundle.stop_bundle, plan.slug)
|
||||
return plan
|
||||
artifact = _ensure_smolmachine(
|
||||
bundle_spec.image,
|
||||
dockerfile=_bundle.SIDECAR_BUNDLE_DOCKERFILE,
|
||||
)
|
||||
launch = _bundle.start_bundle_vm(
|
||||
bundle_spec,
|
||||
from_path=artifact,
|
||||
host_env={**os.environ, **token_env},
|
||||
)
|
||||
stack.callback(_bundle.stop_bundle_vm, plan.slug)
|
||||
|
||||
forward_specs = tuple(
|
||||
_forward.ForwardSpec(
|
||||
label=_label_for_port(container_port),
|
||||
listen_host=proxy_host,
|
||||
listen_port=0,
|
||||
target_host="127.0.0.1",
|
||||
target_port=host_port,
|
||||
)
|
||||
for container_port, host_port in launch.raw_ports.items()
|
||||
)
|
||||
handle = _forward.start_forwarder(forward_specs)
|
||||
stack.callback(_forward.stop_forwarder, handle)
|
||||
published_ports = {
|
||||
_port_for_label(spec.label): spec.listen_port
|
||||
for spec in handle.forwards
|
||||
}
|
||||
return _discover_urls(plan, proxy_host, published_ports)
|
||||
|
||||
|
||||
def _provision_git_gate_keys(
|
||||
@@ -206,35 +228,27 @@ def _provision_git_gate_keys(
|
||||
def _discover_urls(
|
||||
plan: SmolmachinesBottlePlan,
|
||||
proxy_host: str,
|
||||
published_ports: dict[int, int],
|
||||
) -> SmolmachinesBottlePlan:
|
||||
"""Discover host-side ports for published container ports and
|
||||
return the plan with URLs + guest_env stamped in.
|
||||
"""Stamp URLs + guest_env from per-bottle forwarder ports.
|
||||
|
||||
`proxy_host` is the host IP that both TSI's allowlist and
|
||||
docker's port-forward bindings are keyed to. On macOS it is the
|
||||
per-bottle loopback alias; on Linux it is the per-bottle bridge
|
||||
gateway (see `_proxy_host`). The agent dials the published port
|
||||
on this IP for all bundle-hosted services.
|
||||
`proxy_host` is the host IP that both TSI's allowlist and the
|
||||
forwarder listeners are keyed to. The raw smolVM-published ports
|
||||
are intentionally not advertised to the agent.
|
||||
|
||||
NO_PROXY includes `proxy_host` so supervise + git-gate URLs
|
||||
bypass HTTPS_PROXY."""
|
||||
agent_facing_host_port = _bundle.bundle_host_port(
|
||||
plan.slug, _EGRESS_PORT, host_ip=proxy_host,
|
||||
)
|
||||
agent_facing_host_port = published_ports[_EGRESS_PORT]
|
||||
agent_proxy_url = f"http://{proxy_host}:{agent_facing_host_port}"
|
||||
|
||||
agent_git_gate_host = ""
|
||||
if plan.git_gate_plan.upstreams:
|
||||
git_gate_host_port = _bundle.bundle_host_port(
|
||||
plan.slug, _GIT_HTTP_PORT, host_ip=proxy_host,
|
||||
)
|
||||
git_gate_host_port = published_ports[_GIT_HTTP_PORT]
|
||||
agent_git_gate_host = f"{proxy_host}:{git_gate_host_port}"
|
||||
|
||||
agent_supervise_url = ""
|
||||
if plan.supervise_plan is not None:
|
||||
supervise_host_port = _bundle.bundle_host_port(
|
||||
plan.slug, _SUPERVISE_PORT, host_ip=proxy_host,
|
||||
)
|
||||
supervise_host_port = published_ports[_SUPERVISE_PORT]
|
||||
agent_supervise_url = f"http://{proxy_host}:{supervise_host_port}/"
|
||||
|
||||
existing_no_proxy = plan.guest_env.get("NO_PROXY", "localhost,127.0.0.1")
|
||||
@@ -328,26 +342,33 @@ def _init_vm(plan: SmolmachinesBottlePlan) -> None:
|
||||
|
||||
|
||||
def _proxy_host(plan: SmolmachinesBottlePlan, loopback_ip: str) -> str:
|
||||
"""Return the host IP for TSI's allowlist and docker port-forward bindings.
|
||||
|
||||
On macOS, the per-bottle loopback alias (e.g. ``127.0.0.16``) works
|
||||
because macOS's network stack lets TSI intercept 127.x.x.x connects
|
||||
from the guest before they reach the host's own loopback.
|
||||
|
||||
On Linux, the guest kernel's LOCAL routing table routes all
|
||||
``127.0.0.0/8`` to the guest's own loopback — those packets never
|
||||
reach eth0 and TSI never sees them. Using the per-bottle bridge
|
||||
gateway (e.g. ``192.168.N.1``) instead sidesteps the problem: it
|
||||
is not a loopback address, so the guest routes it via eth0 and TSI
|
||||
intercepts it normally. The TSI allowlist is ``gateway/32``, which
|
||||
is distinct from the container IP (``192.168.N.2``), so the agent
|
||||
still can't reach the egress daemon directly — TSI blocks any
|
||||
connection to the container IP that isn't via the published port."""
|
||||
if platform.system() == "Linux":
|
||||
return plan.bundle_gateway
|
||||
"""Return the per-bottle host address used for TSI and forwarders."""
|
||||
del plan
|
||||
return loopback_ip
|
||||
|
||||
|
||||
def _label_for_port(port: int) -> str:
|
||||
if port == _EGRESS_PORT:
|
||||
return "egress"
|
||||
if port == _GIT_HTTP_PORT:
|
||||
return "git-http"
|
||||
if port == _SUPERVISE_PORT:
|
||||
return "supervise"
|
||||
return f"port-{port}"
|
||||
|
||||
|
||||
def _port_for_label(label: str) -> int:
|
||||
if label == "egress":
|
||||
return _EGRESS_PORT
|
||||
if label == "git-http":
|
||||
return _GIT_HTTP_PORT
|
||||
if label == "supervise":
|
||||
return _SUPERVISE_PORT
|
||||
if label.startswith("port-"):
|
||||
return int(label.removeprefix("port-"))
|
||||
raise ValueError(f"unknown sidecar forward label: {label}")
|
||||
|
||||
|
||||
def _bundle_launch_spec(
|
||||
plan: SmolmachinesBottlePlan, network: str, proxy_host: str,
|
||||
) -> _bundle.BundleLaunchSpec:
|
||||
|
||||
Reference in New Issue
Block a user