Annotations
Annotations attach arbitrary key-value metadata to an instance. They use the same key syntax as Kubernetes annotations, so you can reuse your existing keys and tooling.
Unlike tags, which act as controller-level labels that only the platform sees, annotations also reach the guest. You can use them for three things:
- Custom metadata—the instance's startdata includes its annotations, so the guest can read them at runtime.
- Guest network configuration—the guest applies the CNI result in the
unikraft.com/cniannotation to its network interfaces. - Structured log output—you select which annotations the platform injects into the VM's console log output.
Dedicated unikraft CLI subcommands for annotations are coming soon.
In the meantime, drive annotations through the API, which you can invoke with curl or the unikraft api command.
Annotation keys
An annotation is a set of key-value pairs.
A value is any string without ASCII control characters (0x00–0x1f and 0x7f, which covers tab, newline and carriage return), and multi-byte UTF-8 goes through fine.
The API rejects a value carrying an ASCII control character with 400 Bad Request.
Keys follow the Kubernetes annotation key syntax:
- A key can be a plain name (
my-key) or carry an optional DNS prefix separated by a slash (example.com/annotation2). - Each key needs a name segment of at most 63 characters, containing alphanumerics,
-,_, and., and beginning and ending with an alphanumeric character. - The optional prefix is a DNS subdomain (a series of DNS labels joined by
., at most 253 characters) followed by a/. It can't be a wildcard domain.
An instance holds at most 256 annotations.
For example:
Code
Creating an instance with annotations
Add an annotations object to the instance body when you create it.
Each entry is a key-value pair:
Code
Annotation keys are Kubernetes-compatible, so they can be plain keys (my-key) or prefixed with a domain (example.com/annotation2).
Reading annotations from the guest
The platform includes the instance's annotations in its startdata, which the guest reads from /sys/class/uio/uio0/device/startdata:
Code
Code
Your app parses this JSON and reads the annotations object like any other startdata field.
Guest network configuration with CNI
Limited Access
CNI networking is only available in BYOC or on-prem Unikraft Cloud installations. It relies on TAP devices and networks that you set up on the host. To explore such a deal, reach out to the Unikraft Cloud Discord or send an email to support@unikraft.com.
unikraft.com/cni is a reserved annotation.
Its value is a CNI Result document, and the guest applies the addresses and routes it holds to its own network interfaces.
This hands the addressing of an instance to an external CNI plugin or IPAM system. The plugin decides the addresses and routes, writes its result into the annotation, and the guest configures itself from it. Your image needs no agent for it and no boot script of its own.
The annotation pairs with an interface without an address, which reaches the guest as a bare link on a TAP device of your own. See Kubernetes for the CNI integration that Kraftlet drives on the cluster side.
The annotation value is the document as a JSON string, so the JSON inside it carries escapes:
Code
That instance keeps a pool-allocated primary interface and adds a second one on the vpc0 TAP device, with a MAC address you pick and no IP address.
Unescaped, the annotation holds a standard CNI Result (types/100):
Code
The guest gives 192.168.1.5/24 to the link whose MAC address is 0a:58:c0:a8:01:05, and brings that link up.
Writing the document
Give every address a MAC address to land on:
- Each
ips[]entry points through itsinterfaceindex at an entry of the document's owninterfaces[]array, not at the instance'snetwork_interfaces. An entry with nointerfacefield uses index0. - That
interfaces[]entry supplies the MAC address of the interface to configure, sointerfaces[].namecan say anything. Without a MAC address to match, the index counts the instance's non-loopback interfaces from0instead. The entry carries nomac, the document holds nointerfaces[]array, or the index sits past the end of that array. - Set the MAC address yourself when you create the instance, or read it from the instance status.
- Addresses take CIDR notation, IPv4 or IPv6, and the prefix length is mandatory.
- The document needs a
cniVersionof0.3.1,0.4.0,1.0.0, or1.1.0, and has to be a result rather than a network configuration. - Keep the document on one line, because annotation values reject control characters.
Only interfaces[].mac, ips[].interface, ips[].address, ips[].gateway, and the routes array have any effect.
dns, mtu, and sandbox do nothing, so you can pass a plugin's full result through untouched.
Set the resolver with the instance's own nameserver field.
The guest doesn't use stateless address autoconfiguration (SLAAC), so it never takes IPv6 addresses or routes from router advertisements.
The instance's own addresses and gateway field are IPv4 only, which leaves the document as the only source of IPv6 addresses.
List an IPv6 default route in the document, or configure one inside the guest, if the workload needs it.
The guest also skips duplicate address detection, so its IPv6 link-local addresses and the addresses from the document are usable directly after boot.
Routes
The guest installs each entry of the document's routes array in its own routing table:
Code
The guest sends traffic for 10.2.0.0/16 through 192.168.1.254, and traffic for 10.3.0.0/16 through 192.168.1.1, the gateway of the matching ips entry.
Each route accepts the following fields:
| Field | Required | Description |
|---|---|---|
dst | Yes | The destination in CIDR notation, IPv4 or IPv6, such as 0.0.0.0/0 for a default route. |
gw | No | The next-hop address in the same IP family as dst, with the fallback described under picking the next hop. |
priority | No | The route metric, where a lower value wins. |
table | No | The numeric ID of the routing table to add the route to, which defaults to the main table. |
scope | No | The numeric route scope, such as 0 for global, 253 for link, and 254 for host, which defaults to 0 for a route with a next hop and 253 for a route without one. |
The guest ignores mtu, advmss, and any other route field.
A document holds at most 16 routes.
Picking the next hop
A route without a gw uses the gateway of the matching ips entry, which is the first entry with an address of the route's IP family and a gateway of its own.
When no entry has a gateway, the route has no next hop, and the guest sends its traffic straight onto the link.
Routes carry no interface of their own, so the guest picks one:
- The interface whose address prefix contains the route's next hop.
- Otherwise, the interface of the first
ipsentry with an address of the route's IP family.
Every route needs at least one address of its own IP family in the document.
Default routes
A gateway in ips creates no route on its own.
List the default route explicitly, as { "dst": "0.0.0.0/0" } for IPv4 or { "dst": "::/0" } for IPv6, and the guest uses that gateway as its next hop.
Route order
The guest adds routes after it configures the addresses, in the order of the routes array.
List a route that makes a gateway reachable ahead of the routes that use that gateway.
Routing tables
A route with a table other than the main table only takes effect once a policy routing rule points traffic at that table.
The guest adds the route but never adds such a rule, so the operator may need to add one inside the guest, for example with ip rule add from 192.168.1.5 lookup 100.
Changing the configuration
The guest applies the document at boot, and again whenever the instance resumes from a snapshot, for example after scale-to-zero. A patched annotation takes effect once the instance stops, so the guest reads the new document on its next boot or resume.
On a resume, the guest drops the addresses and routes it added that the document no longer lists, and adds the new ones, without a reboot. The guest never removes an address or a route it didn't add itself.
When the document fails
At boot, a document that fails to parse or apply stops the instance. That covers invalid JSON, a MAC address that matches no interface, a route with no address of its IP family, and a route that the guest kernel rejects. When the instance resumes from a snapshot, the same failure leaves the instance running and only logs it.
Check that each MAC address in the document matches one on the instance, and that each route has an address to use. Then read the instance logs, which name any entry that fails to parse or apply.
Patching annotations
Update the annotations on an existing instance with a PATCH /instances request.
The body is an array of patch operations, each naming the target instance, the annotations property, and one of three operations: set, add, or del.
Annotation changes apply only while the instance is in the stopped state.
If you patch a running instance, the platform accepts the request and queues the change, then applies it the next time the instance stops.
Replace all annotations (set)
set replaces all existing annotations with the object you provide:
Code
Add or update annotations (add)
add merges the given annotations into the existing set.
It overwrites keys that already exist and leaves the other keys untouched:
Code
Delete annotations (del)
del removes specific keys.
The value is an array of key names (or a single key as a string):
Code
Annotations in log output
Beyond the guest, the platform can inject selected annotations into a VM's console log output. This helps with structured logging to an external collector.
This forwarding happens at the node level rather than through the instances API, so coordinate with Unikraft to enable it for your deployment. The platform exposes two options:
Code
--vmm-console-portsdefines named console ports as<name>:<type>[/<format>][:<path>]. Hereappandapp2aresocketports using thejsonformat, each writing to a Vector socket.--vmm-console-annotationsselects which annotations appear in each port's output, as<port>:<key1>+<key2>,...or<port>:*:app:my-key+test.unikraft.com/my-annotation—forward only the listed annotation keys (joined with+) to portapp.app2:*—the*wildcard forwards all annotations to portapp2.
When the guest writes to the app console (for example, echo "blub" > /dev/vport2p1), the collector receives the message together with the selected annotations:
Code
Annotation events
The platform's event log carries a vm.annotate event, which it emits whenever an instance's annotations change:
Code
An external system consumes the event to keep track of the instance's annotation values, without polling the instance API.
The event also gives log processing a way to map an instance UUID back to its annotations.
The audit event stream doesn't carry vm.annotate, so read it from the node event log.
Limitations
- An instance holds at most 256 annotations.
- Annotation keys must follow the Kubernetes key syntax, and values are strings with no ASCII control characters in them.
- A patched annotation change applies once the instance reaches the
stoppedstate. - A
unikraft.com/cnidocument holds at most eight interfaces, 16 addresses, and 16 routes, and anips[].interfaceindex stays below eight. - The guest applies only
interfaces[].mac,ips[].interface,ips[].address,ips[].gateway, androutesfrom that document. - Applying
routesrequires the latest base-compat guest kernel. An instance that resumes from a snapshot keeps the kernel that the snapshot captured, and applies routes only after it cold-boots on the latest kernel. - Dedicated
unikraftCLI subcommands aren't available yet, so drive annotations through the API. - The platform sets up log-output forwarding at the node level, not through the instances API.
- The
vm.annotateevent needs switching on in the node's event configuration.
Learn more
- Tags: controller-level labels for organizing platform resources.
- Custom network configuration: bring your own interfaces, MAC addresses and TAP devices, including interfaces without an address for a CNI plugin to fill in.
- Kubernetes: how Kraftlet runs the CNI plugins of your cluster for Unikraft Cloud instances.
- Instances: create and manage instances.
- Unikraft Cloud's REST API reference, in particular the section on instances.