Worked Example - Build a Three-Tier VPC via API

This page shows the API calls required to build a three-tier VPC with an internet-facing load balancer, DNS, and DHCP, in the order they must be issued.

Five of the calls depend on behaviour that is not obvious from the individual endpoint pages. Each of those is called out inline so the sequence can be followed end-to-end without discovering a dependency by trial.

Prerequisites

  • Karios control plane reachable at https://<mgmt>/api/v2.

  • A valid Bearer token (see API Reference under the user-authentication tag).

  • For VXLAN: a fabric registered via POST /controlnode/fabric-config. Creating the VXLAN physical network needs no more than that; the VXLAN guest networks, VPC tiers and VM deploys later in this sequence also need the fabric to be converged (see Fabric readiness gates guest networks, tiers and VMs - not physical networks).

Sequence

Thirteen calls, in this order. All bodies and response shapes are documented per-endpoint in the API Reference; this page names the calls and the ordering constraints between them.

#

Call

Ordering note

1

GET /controlnode/fabric-config

Precondition. A 204 here means no fabric is registered - register one before creating anything VXLAN. See Fabric readiness gates guest networks, tiers and VMs - not physical networks for what rollout_status: ready does and does not gate.

2

POST /network/vpc-offerings/ensure-for-size

Returns the offering used in step 4. See Isolation mode is carried in the offering name suffix about isolation mode being carried in the composed name suffix. Pass redundant_router: true here if you want an HA router pair - see Redundancy is chosen at the offering, not on the VPC.

3

GET /ipam/vlans

Required in practice: every Karios VPC tier offering sets SpecifyVLAN, so each tier created in step 7 needs a VLAN/VNI. Take vid and prefix_id for each tier now. (SpecifyVLAN belongs to the tier offering used in step 7, not to the VPC offering from step 2.)

4

POST /network/vpcs

Redundancy is not chosen here - redundant_vr is legacy and has no effect. See Redundancy is chosen at the offering, not on the VPC.

5

POST /network/vpcs/acl-lists

One per tier.

6

POST /network/vpcs/acl-lists/items

Set number explicitly for every rule. See ACL rule order must be set explicitly.

7

POST /network/vpcs/tiers

One per tier. Waits on the VPC’s virtual router being up.

8

POST /ddi/public-ips

Use this path to allocate the address, not /network/public-ip-addresses. DNS and PTR records are opt-in: send create_dns: true, hostname, dns_zone and create_ptr: true alongside zone_id / network_id, or no DNS record is created. See The DDI path is the only one that can write DNS - and only if you ask.

9

POST /network/public-ip-addresses/static-nat/enable

Always pass public_ip. Omitting it enables NAT but leaves the IPAM record stale - a silent divergence with no later error.

10

POST /network/load-balancer/rules, then .../rules/assign

Rule first, then membership. Read membership back with /rules/instances (see the endpoint page for the applied / lbvmips flags).

11

POST /network/port-forwarding

Standard.

12

POST /ddi/dhcp/scopes, then /ddi/dns/zones and /records

DHCP scope creation is independent of the network - pass network_id to bind it.

13

GET /network/guest-networks/jobs/latest

Poll until every node reports ok. Response contains bridge_result.failed_nodes for partial failures.

Caveats you cannot derive from the reference alone

Fabric readiness gates guest networks, tiers and VMs - not physical networks

Creating a VXLAN physical network requires only that a fabric is registered at all - GET /controlnode/fabric-config returning 204 No Content means it is not, and that is the single state which refuses the create (with 412 Precondition Failed). A rollout that is still pending or rolling out, or that has finished failed or degraded, is deliberately allowed: the physical network is only CloudStack metadata and no overlay traffic rides it yet, and physical networks are created during cluster bring-up, before the fabric converges.

The strict gate applies to the later calls that do put traffic on the overlay - VXLAN guest-network creation, VPC tier creation (step 7), and deploying a VM onto a VXLAN network (network update and restart are gated the same way). Those require all of:

  • rollout_status: ready,

  • a settled MTU rollout - an MTU rollout still in flight blocks them,

  • every fabric node healthy: one node reporting failed or unreachable blocks new VXLAN creates cluster-wide until it converges.

Failures here are runtime errors, and all of them are retriable once the fabric converges.

Isolation mode is carried in the offering name suffix

/network/vpc-offerings/ensure-for-size returns an offering whose composed name (for example karios-vpc-vxlan-medium-redundant) carries the isolation decision (vlan vs vxlan). This drives Karios-layer pairing validation when tiers are later created against it. Renaming a composed offering breaks pairing silently.

Redundancy is chosen at the offering, not on the VPC

Pass redundant_router: true to step 2 (/network/vpc-offerings/ensure-for-size). That composes the -redundant variant of the offering (for example karios-vpc-vxlan-medium-redundant), which carries the redundant-router capability, and the VPC created from it in step 4 is redundant from birth.

/network/vpcs still accepts a redundant_vr field, but it is legacy: the create path does not read it. Sending it changes nothing and reports nothing - the response looks identical and the VPC comes up with a single router. Decide redundancy in step 2.

Do not try to promote an existing VPC afterwards by restarting it with CloudStack’s make-redundant option either: that moves the VPC onto CloudStack’s own redundant VPC offering and discards the composed Karios offering name that tier pairing keys on (see Isolation mode is carried in the offering name suffix). Recreate the VPC from a -redundant offering instead.

ACL rule order must be set explicitly

/network/vpcs/acl-lists/items accepts number as the rule ordering key. Omit it and CloudStack picks one for you, from the highest number currently in the list - so two rules created in parallel both read the same “highest” value and race for the slot that follows it. Nothing in the Karios layer de-conflicts this: number is forwarded to CloudStack exactly as sent, and is only omitted from the call when you omit it. The resulting rule order follows arrival order, which for a security ACL is a policy bug, not a display glitch.

Set number explicitly on every rule, and reject the call locally if two of your own rules would collide. Numbering in steps of ten (100, 110, 120) leaves room to insert rules later without renumbering the list.

The DDI path is the only one that can write DNS - and only if you ask

Three surfaces can allocate a public address: /network/public-ip-addresses, /ddi/public-ips, and /network/vpcs/public-ips/acquire. All three perform the same underlying CloudStack address association, so the address itself is identical whichever you use.

/ddi/public-ips is the only one that can also create the matching DNS and PTR records - but it does not do so by default. Those fields are optional and unset unless you send them:

POST /ddi/public-ips
{
  "zone_id":    "<zone-uuid>",
  "network_id": "<network-uuid>",
  "create_dns": true,
  "hostname":   "lb-01",
  "dns_zone":   "example.com",
  "create_ptr": true
}

Called with only zone_id and network_id, this endpoint creates no DNS record and no PTR record - the same outcome as the other two paths, with no error to tell you so. A DNS failure is non-fatal too: the address stays allocated and the failure is reported in the response message rather than as an error status, so check the response, not just the status code.

IPAM works the other way round. The two /network/... paths record the address in Karios IPAM as part of the call; /ddi/public-ips does not, and an address allocated there is picked up only by the background CloudStack to IPAM reconciliation.