- I can start a thread, if I want shared memory parallelism
- I can start a process, if I want shared storage parallelism
- I cannot start a service, if I want shared network parallelism
What would it take to add a start
API?
from threading import Thread
def task(d, k, v):
d[k] = v
values = {}
threads = [
Thread(target=task, args=(values, 1, 'hello')),
Thread(target=task, args=(values, 2, 'world')),
]
for t in threads:
t.start()
for t in threads:
t.join()
assert values == {1: 'hello', 2: 'world'}
from multiprocessing import Process
from pathlib import Path
def task(k, v):
open(k, 'w+').write(v)
processes = [
Process(target=task, args=('1.txt', 'hello')),
Process(target=task, args=('2.txt', 'world')),
]
if __name__ == '__main__':
assert Path('1.txt').exists() == False
assert Path('2.txt').exists() == False
for p in processes:
p.start()
for p in processes:
p.join()
assert open('1.txt', 'r').read() == 'hello'
assert open('2.txt', 'r').read() == 'world'
# Why can't I do this? π€·πΎββοΈ
with Redis() as redis_service:
services = [
Container(
image="redis",
command="redis-cli", args=('-h', 'redis.local', 'SET', '1', 'hello'),
links={'redis': redis_service}
),
Container(
image="redis",
command="redis-cli", args=('-h', 'redis.local', 'SET', '2', 'world'),
links={'redis': redis_service}
),
]
redis_client = redis.Redis(host=redis_service.address)
assert redis_client.get('1') == None
assert redis_client.get('2') == None
for s in services:
s.start()
for s in services:
s.join()
assert redis_client.get('1') == 'hello'
assert redis_client.get('2') == 'world'
The caternetes paper got me thinking, and so the question I'm asking at least recapitulates:
- Spencer Baugh (
catern
)'s rsyscall - Plan9's cpu server
- Erlang's OTP
In terms of ecosystems, there's really just containers and virtual machines; sorry jails and chroot/pledge/unveil.
Too high level. Or low-level, depending on your point-of-view. AFAICT no standard way to inherit (or pass)...
- Networking fabric
- Inputs / output / error streams
- Service links
- Resource contraints
- ... ?
Perhaps this is too quick of a dismissal; Firecracker works for Lambda/Fargate, Kata, and Fly.io.
- systemd's container interface (see also: systemd-nspawn / machinectl)
- Docker's Engine API as accessed via
/var/run/docker.sock
- k8s pods can access the API ...
- ... but wow, no. However, the kublet's API seems reasonable
- Both k8s and Docker front containerd's CRI as accessed via
/var/run/containerd.sock
Complexity is conserved; is cannot be removed or hidden, only rearranged. βΒ Law of conservation of complexity
This is a hard enough problem that there's a Container Networking Interface (CNI) standard!
IM-extrememly-HO, connections in and out of a service are fundamentally "links" between services.
Basically, services should only be able to talk with:
- Their parent service and services it linked
- Their child services
Idea: services connected and isolated via a mesh network ala tailscale
Flippantly: Tailscale Funnel.
Seriously: π€·πΎββοΈ
Public egress/ingress is as hard as we let it be. CNI is all about interfaces, IPs, routes, and DNS.
Defer it. Interfaces are first-class capabilities exposed by a service host node ala Plan 9's ip
device.
A capability that's divisable (range of total capacity for blocks, sub-directory for filesystems). Exposed by service host nodes or dedicated (network accessible) nodes.
Yes, of course Kubernetes has a Container Storage Interface (CSI).
Would it be the worst to put (non-ephemeral) storage under the network-accessible capability bucket? Yes, that's fine.
The currently running service inherits a larger available pool than it has reserved. That available portion is a divisible capability. Available >= Reserved >= In-Use
.
Is there a way to simplify this model to Available >= In-Use
? Noβ that would play hell with garbage collection. It also would require coordination across service host boundaries.
- Every service has an address (IPv6 ULA)
- A parent can access a child's address
- A parent can give a child access to any addresses the parent can access (inc. itself)
cpu
command in Go, inspired by the Plan 9 cpu command- Using Tailscale with Docker
- Fly.io: Working with the Machines API
- Idea: FLAME but a multilingual supervision tree via WASM