Reverse-specification of zun — branch stable/2026.2
- Repository: https://opendev.org/openstack/zun — specified branch:
stable/2026.2@34135a563699— analysed on: 2026-11-01 - Scope: all processes and all axes (API, errors, interactions, scenarios, asynchronous processing) plus the rewrite pack. Change history (step 7) not requested.
Overview
Zun is the OpenStack Containers service: a REST API that runs application containers directly on compute nodes, without a virtual machine and without a user-managed cluster (README.rst:12, doc/source/install/overview.rst:5). A user asks for an image, resources, networks and volumes. zun-api stores the request, picks a node with Placement and sends a message. zun-compute on that node then drives the local Docker daemon, or containerd through CRI for capsules (pod-like groups). Containers get Neutron ports through kuryr-libnetwork or a CNI plugin, Cinder volumes are mounted on the node and bind-mounted, and images come from a Docker registry or from Glance. The default runtime is runc, which shares the node's kernel; Kata is an operator option. There is no conductor, no notification and no evacuation: a dead node leaves its containers in place, and the API answers 500 for them.
Architecture
flowchart LR
client[Client: zun CLI, openstack appcontainer, Skyline, Heat] -->|HTTPS REST, port 9517| api[zun-api]
client -->|WebSocket, token| ws[zun-wsproxy]
api -->|REST token check| ks[Keystone]
api -->|REST candidates and allocations| pl[Placement]
api -->|REST networks, security groups| ne[Neutron]
api -->|REST volumes| ci[Cinder]
api -->|SQL| db[(MariaDB)]
api -->|AMQP cast and call| mq{{RabbitMQ}}
mq --> c[zun-compute on each node]
c -->|SQL| db
c -->|REST inventory| pl
c -->|REST ports| ne
c -->|REST attach, plus os-brick| ci
c -->|REST image file, optional| gl[Glance]
c -->|Docker API, unix socket| d[Docker]
c -->|CRI gRPC, unix socket| cd[containerd]
d -->|network plugin| ku[kuryr-libnetwork]
ku --> ne
cd -->|CNI| cni[zun-cni]
cni -->|HTTP 127.0.0.1:9036| dm[zun-cni-daemon]
dm -->|REST ports| ne
dm -->|SQL| db
ws -->|SQL| db
ws -->|Docker API, tcp 2375| d
d -->|docker pull| rg[Registry, Docker Hub by default]Processes
| Process | Entry point | Role | Listens to / consumes |
|---|---|---|---|
zun-api | zun/cmd/api.py:29, zun/api/wsgi.py:1 (setup.cfg:32) | REST API, scheduling in-process | port 9517 |
zun-compute | zun/cmd/compute.py:32 | runs containers, inventory, state sync | AMQP topic zun-compute and zun-compute.<host> |
zun-wsproxy | zun/cmd/wsproxy.py:27 | WebSocket proxy for attach and exec | [websocket_proxy], port in zun/conf/websocket_proxy.py:54 |
zun-cni-daemon | zun/cni/cmd/cni_daemon.py:25 | plugs VIFs for capsules and the cni driver | HTTP 127.0.0.1:9036 |
zun-cni | zun/cni/cmd/cni.py:33 | CNI plugin binary, a thin client of the daemon | called by containerd or by zun-compute |
zun-db-manage | zun/cmd/db_manage.py:59 | Alembic migrations | — |
zun-status | zun/cmd/status.py:65 | upgrade checks | — |
External dependencies
| System | Protocol | Library | Configuration | Usage |
|---|---|---|---|---|
| MariaDB or MySQL | SQL | oslo.db, SQLAlchemy | [database]/connection | all state |
| RabbitMQ | AMQP | oslo.messaging | transport_url | API to node commands |
| Keystone | REST | keystonemiddleware, keystoneauth | [keystone_authtoken], [keystone_auth] | authentication |
| Placement | REST | keystoneauth | [placement_client] | inventory, candidates, allocations |
| Neutron | REST | python-neutronclient | [neutron_client] | ports, security groups, networks |
| Cinder | REST, plus iSCSI, RBD or others | python-cinderclient, os-brick | [cinder_client], [volume] | volumes (optional) |
| Glance | REST | python-glanceclient | [glance_client], [glance] | images (optional) |
| Docker | Docker Engine API | docker-py | [docker] | containers, images, networks |
| containerd | CRI gRPC runtime.v1alpha2 | grpcio | fixed socket zun/container/cri/driver.py:45 | capsules (default capsule driver) |
| kuryr-libnetwork | Docker network plugin, called by Docker | — | [network] | containers, default network driver |
| Docker registry | Registry API, called by the daemon | — | [docker]/default_registry, registry table | image pulls |
Files of this report
| File | Content |
|---|---|
| 01-rest-api | endpoints, functional sheets, lifecycles, spec and code gaps |
| 02-errors | exceptions, situations, translation, asynchronous errors |
| 03-interactions | RPC, database, outbound REST, Docker, CRI, CNI, isolation settings |
| 04-scenarios | sequence diagrams and diagnostics |
| 05-async-processing | RPC handlers, timers, start-up recovery, CNI daemon, WebSocket proxy |
| 07-rewrite-pack | contracts, client expectations, conformance strategy |
| _verification | reference check output |
[_inventory/](_inventory/inventory_stable-2026.2.md), [_rewrite/](_rewrite/rewrite-pack-index_stable-2026.2.md) | raw inventory; machine-readable contracts |
Key figures
- 63 routes for 73 handlers, 40 microversions, 101 JSON schemas, 82 policy rules ([
_rewrite/](_rewrite/rewrite-pack-index_stable-2026.2.md)). - 33 RPC methods on the client, 2 of them dead; casts run in a green thread on the node.
- 6 timers on the nodes and in the CNI daemon, one recovery routine at start-up.
- 19 tables in models and migrations, 84 migrations, head
3f2b36231bee, 4 model and migration differences. - 155 exception classes in the inventory.
- 221 API unit tests recorded into 256 exchanges, all passing.
Points of attention
- Isolation rests on the runtime. With the default
runc, all containers on a node share its kernel, and nothing in the scheduler separates projects (zun/conf/container_driver.py:55,zun/conf/scheduler.py:64). Zun sets noseccomp, capabilities, user namespace or PID limit (03 § Isolation).privilegedis denied by default (zun/common/policies/container.py:43). - The Docker TCP port is part of the design.
zun-wsproxyreaches Docker on 2375, and the install guide opens it without TLS (doc/source/install/compute-install.rst:224,zun/conf/docker.py:49). Access to that port is root on the node [general knowledge]. - Console tokens do not expire and are not checked against Keystone (
zun/websocket/websocketproxy.py:210,zun/compute/manager.py:933). - Image cache is per node and not per project: with
ifnotpresentornever, an image is used as found, whoever pulled it (zun/image/docker/driver.py:90). Choosing a policy is admin-only by default (zun/common/policies/container.py:67). - Real bugs seen at run time: out-of-range microversion and wrong
Content-Typecrash the error path instead of giving 406 and 415 (zun/api/http_error.py:65,zun/api/utils.py:89).auto_healnever rebuilds (zun/container/docker/driver.py:518). - **
NoValidHostis a 202**, then statusError(zun/compute/api.py:57). - **
ContainerHostNotUpis a 500**, with an obfuscated message (zun/common/exception.py:371). - No reconciliation with Placement on some failure branches, and no evacuation of a dead node (05).
- Full-table reads per node per minute in
sync_container_stateand indelete_unused_containers(zun/compute/manager.py:1240,zun/compute/manager.py:1216). - Tokens in messages: the user's token travels in the RPC context and may be used long after the request (
zun/common/keystone.py:72).
Blind spots / [inferred]
- No running deployment: frames are reconstructed from the code, no real Docker, Kuryr, Neutron or Cinder was exercised. The API tests mock the scheduler and compute.
zun/pci/and SR-IOV paths were read only where they touch creation.zun/compute/provider_tree.pyandzun/scheduler/client/report.pycome from Nova and were read through their entry points only.contrib/(old nova-docker driver) is out of scope. ItsFixedIntervalLoopingCallin the inventory is not a Zun process.kuryr-libnetworkis not in the workspace; its behaviour is described from the Zun side only.- Mappings marked [inferred] in each file were not followed to the end.
Source: reverse-spec-zun/00-summary_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.