Skip to content

Add SONiC containerlab (docker-sonic-vs) support#3680

Open
roc-ops wants to merge 3 commits into
ipspace:devfrom
roc-ops:sonic-containerlab
Open

Add SONiC containerlab (docker-sonic-vs) support#3680
roc-ops wants to merge 3 commits into
ipspace:devfrom
roc-ops:sonic-containerlab

Conversation

@roc-ops

@roc-ops roc-ops commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Add SONiC containerlab (docker-sonic-vs) support

Summary

netlab already ships a sonic device, but only as a libvirt Vagrant box (the Azure/VM
SONiC image) with minimal support declared — initial configuration and BGP. This PR adds
SONiC support under containerlab using the community
docker-sonic-vs image, and expands the
declared/validated SONiC module coverage well beyond initial + BGP.

The new module coverage is attached to a child device sonic_clab (parent: sonic) rather
than to the existing libvirt device, because that coverage was developed and validated against
the docker-sonic-vs containerlab image and its access model specifically — a single monolithic
container with FRR vtysh running directly in it, configured over Ansible's docker connection
plugin. The libvirt SONiC VM has a different access model (FRR runs in a nested bgp
sub-container reached via docker exec bgp vtysh), so the templates and config-push path here
are not the ones exercised on the VM. Scoping the new support to the child device keeps each
device advertising only what has actually been validated on its image; it is not a claim that
the VM is incapable of these features.

sonic_clab inherits interface/loopback naming and the base BGP feature set from sonic,
adds a clab: block, and provides its own initial/bgp/vlan/lag/vxlan/vrrp.version
templates where the monolithic image requires it. Every other module is delegated to the in-tree
FRR templates via the inherited ansible_network_os: frr.

No netlab-core changes. netsim/cli/, netsim/augment/, netsim/attributes.yml,
netsim/providers/, and netsim/modules/ are byte-identical to dev. mypy -p netsim reports
Success: no issues found in 339 source files and the transformation test suite passes
284 passed, 1 skipped, 0 failed (run in a clean venv from requirements.txt +
requirements-dev.txt).

Design note

Added as a child device sonic_clab (parent: sonic), following the existing
iol/ioll2, csr/cat8000v, vjunos-router/vjunos-switch precedent — same NOS, a
different deployment image/shape and validated feature set, expressed as a separate device so the
new coverage attaches to the containerlab image without changing the existing libvirt sonic
device. Happy to rename or restructure if you'd prefer a different approach (e.g. a different
child-device name, or modelling it as a provider/image variant of sonic) — just let me know.

Connection / config-deployment model

Configuration is deployed over Ansible's built-in docker connection plugin (docker exec),
not network_cli — the same pattern netlab already uses for the in-tree frr containerlab
device. docker-sonic-vs ships no running sshd and no cliconf-compatible CLI (the only SONiC
cliconf Ansible ships, dellemc.enterprise_sonic, targets Dell's licensed Management-Framework
CLI, which the community image does not contain). Routing config is rendered as FRR vtysh
scripts; initial/vlan/lag use SONiC's native config CLI plus a deterministic
CONFIG_DB→kernel sync where the VS orchestration agents don't program the datapath.

FRR daemons that ship disabled in /etc/frr/daemons are enabled per configured module during
initial configuration.

Supported modules

ospf (incl. OSPFv3, ospf.areas), isis (v4/v6), bgp (+ bgp.session, bgp.policy,
bgp.originate, bgp.domain, ebgp.multihop), ripv2, bfd, vrf (incl. route-leaking,
per-VRF OSPFv2/OSPFv3/IS-IS/BGP), vlan (L3 switch / SVIs), lag, vxlan, evpn (VXLAN and
MPLS transport, symmetric IRB / L3VNI, evpn.multihoming), mpls (LDP + L3VPN), sr (SR-MPLS
via IS-IS), srv6, gateway/vrrp.version, routing (static/prefix/aspath/community/policy),
and tunnel.gre.

Support level: best-effort — broad module coverage, each module carrying a device
integration topology under tests/integration/platform/sonic_clab/; the image is user-supplied
and the device is not (yet) wired into the automated per-release integration matrix.

Verification and provenance

Live-verified for this submission against a real docker-sonic-vs:latest container on
containerlab, config + validation over docker exec:

Module How verified Result
OSPF netlab validate (native docker-exec) Tests passed: 2/2 (both neighbors Full)
BGP netlab validate (native docker-exec) Tests passed: 2/2 (eBGP Established both ways)
IS-IS netlab validate (native docker-exec) Tests passed: 2/2 (adjacency Up both ways)
VRF per-VRF OSPF neighbor state Full in both directions in VRF red
VLAN SVI-to-SVI datapath ping 0% loss; access port master Bridge state forwarding (CONFIG_DB→kernel bridge sync confirmed)

Supported, with integration topologies included but not re-run for this submission: the
remaining modules (EVPN / symmetric-IRB / evpn.multihoming, MPLS L3VPN, SR-MPLS, SRv6, RIPv2,
BFD, tunnel.gre, the BGP plugins, and the IPv4/IPv6 dual-stack variants) each ship a topology
under tests/integration/platform/sonic_clab/ (several with native validate: sections) that was
exercised during the device's development. They are presented as supported, not as re-verified in
this PR.

Caveats / limits

Documented in docs/caveats.md (caveats-sonic-clab) and flagged in the platform support
tables:

  • You supply your own docker-sonic-vs:latest image; netlab does not ship it.
  • MPLS / SR-MPLS labs need a one-time host prerequisite: sudo modprobe mpls_router mpls_iptunnel (loads net.mpls into every container netns → real kernel label FIB). Flagged
    with a caveat marker on the MPLS/SR-MPLS cells in the support tables.
  • SRv6 is control-plane + kernel-plane only on this image: locator/SIDs are advertised in the
    IS-IS LSDB and installed as real kernel seg6local routes, but end-to-end SRv6-OAM datapath
    (ping to a uN End SID) is not resolved — the same open item seen with FRR/IS-IS SRv6
    elsewhere. Marked with a caveat in the support table.
  • Genuinely unsupported on this image (probed, not merely untested): DHCP relay/client (no
    dhcrelay/dhcp6relay binary), real STP port-blocking (no stpd daemon), and the
    mlag.vtep active-active EVPN datapath (no mclagd). These are shown as ✗/absent in the
    tables, not claimed.

Files

  • netsim/devices/sonic_clab.yml — device definition (parent sonic, clab: block, features).
  • netsim/ansible/templates/{initial,bgp,vlan,lag,vxlan}/sonic_clab.j2,
    netsim/extra/vrrp/version/sonic_clab.j2 — device-specific templates.
  • netsim/ansible/tasks/{deploy-config,readiness-check}/sonic_clab.yml — docker-exec deploy.
  • netsim/validate/{,ospf,bgp,isis,route}/sonic_clab.py — FRR validation re-exports.
  • tests/integration/platform/sonic_clab/ — integration topologies + README.
  • docs/platforms.md, docs/caveats.md, docs/labs/sonic.md, docs/release/26.07.md — docs.
  • netsim/extra/vrrp/version/defaults.yml — registers the none meta-device for the
    vrrp.version plugin so tests/check-integration-tests.sh can transform the 10-vrrp-version
    topology under -d none, mirroring the existing evpn.multihoming plugin. This is the only
    change outside the sonic_clab device + docs.

@roc-ops
roc-ops force-pushed the sonic-containerlab branch 2 times, most recently from 07be6d2 to 281a6ae Compare July 21, 2026 20:16
Comment thread docs/caveats.md Outdated
SSH plays no part in configuration deployment.
* Most FRR daemons ship disabled in `/etc/frr/daemons` by default (to save resources); the
initial configuration enables the ones the configured modules need and restarts FRR once.
* MPLS/SR-MPLS labs need a one-time host prerequisite before `netlab up`: `sudo modprobe

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a mechanism for this, see kmods:

Since Sonic runs FRR inside, it may be better to inherit the FRR settings where possible, rather than redefine them

# Configure interfaces
#
config hostname {{ inventory_hostname.replace("_","-") }}
{% for l in netlab_interfaces|default([]) if l.type == 'lag' %}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a recently added mechanism for running module specific initialization during init, see VyOS templates for an example

config portchannel add {{ l.ifname }}
fi
{% endfor %}
{% if vlans is defined %}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This belongs in the vlan module, you could use vlan/sonic.initial.j2 similar to vyos

#
# And now let's configure the interfaces
#
cat >/tmp/netlab-initial.frr <<CONFIG

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably reuse FRR scripting

@@ -0,0 +1,33 @@
#!/bin/bash

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be named sonic-clab.j2 I believe (i.e. dash)

@@ -0,0 +1,75 @@
#!/bin/bash

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sonic-clab.j2

@@ -0,0 +1,91 @@
#!/bin/bash

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible reuse FRR scripts

vrf: [ bgpd ]
gateway: [ vrrpd ]
mpls: [ ldpd ]
clab:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add empty kmods: key to enable kernel module loading support

circuit_type: true
unnumbered: { ipv4: true, ipv6: true, network: true }
import: [ static ]
mpls:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's up to Ivan, but I imagine it might be better to break this up into smaller PRs: First add initial support, then add OSPF, BGP, etc.

Comment thread netsim/extra/vrrp/version/defaults.yml Outdated
dellos10:
features.gateway.vrrp.version: True
# the meta device used by tests/check-integration-tests.sh has to accept the plugin
none:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This belongs in a separate PR

Comment thread netsim/extra/vrrp/version/sonic_clab.j2 Outdated
interface {{ intf.ifname }}
vrrp {{ intf.gateway.vrrp.group }} shutdown
vrrp {{ intf.gateway.vrrp.group }} version {{ gateway.vrrp.version|default(3) }}
no vrrp {{ intf.gateway.vrrp.group }} shutdown

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user could have requested that the interface be shutdown, the logic should check that

Comment thread netsim/extra/vrrp/version/sonic_clab.j2 Outdated
#!/bin/bash
#
# vrrp.version plugin — FRR vrrpd supports both VRRP v2 and v3. netlab
# renders this as a separate per-node config step; FRR requires the vrrp instance

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik none of the existing devices supports changing VRRP versions mid-flight, it's a nice detail but also a bit overkill?

@jbemmel jbemmel left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for your sizable contribution, it's appreciated!

Overall, I think our preference would be to reuse FRR logic and templates as much as possible, rather than reimplement the same thing in parallel. You already do that in some places like validation scripts, since Sonic is running standard FRR (I presume) the configuration should be fairly similar (if not identical)

I also think a 50 file PR is perhaps a bit too big of a chunk to accept in one go, but I'll let Ivan comment

@roc-ops
roc-ops force-pushed the sonic-containerlab branch from 281a6ae to 1d885ff Compare July 21, 2026 21:36
roc-ops added 3 commits July 22, 2026 01:49
Add a sonic_clab device (parent: sonic) for the community docker-sonic-vs image
running under containerlab, alongside the existing libvirt SONiC VM device.

Since SONiC runs standard FRR, the device reuses FRR wherever the config is FRR:
every routing module -- including bgp -- is delegated to the in-tree FRR
templates via ansible_network_os: frr (no parallel routing templates), the
single validation plugin re-exports the FRR checks (from netsim.validate.frr
import *), and a clab.kmods declaration (mirroring frr.yml) lets netlab
auto-load the mpls/vrf/vxlan kernel modules.

What stays SONiC-specific is only what genuinely is not FRR: interface, VLAN and
PortChannel objects are created through SONiC config_db (the config CLI), FRR
daemons that ship disabled in docker-sonic-vs are enabled per configured module,
configuration is deployed over the Ansible docker connection (docker exec), and
a deterministic CONFIG_DB->kernel sync backs the modules the VS orchestration
agents do not program. VLAN and PortChannel creation lives in the vlan/lag
module-init hooks (vlan|lag/sonic_clab.initial.j2), pulled into initial config
by extra_module_initial().

Module coverage: OSPF/OSPFv3, IS-IS, BGP (+ bgp.session/policy/originate/domain,
ebgp.multihop), RIPv2, BFD, VRF, VLAN, LAG, VXLAN/EVPN (incl. symmetric IRB and
multihoming), MPLS L3VPN, SR-MPLS, SRv6, gateway/VRRP, routing, tunnel.gre.
No netlab-core changes.
Device bring-up and per-module smoke tests for the sonic_clab device, each
verified live against a docker-sonic-vs container over the docker connection
plugin. Covers initial, OSPF/OSPFv3, IS-IS (v4/v6), BGP and its plugins, RIPv2,
BFD, VRF and route-leaking (v4/v6), VLAN, LAG, VXLAN/EVPN, symmetric IRB,
EVPN multihoming, MPLS/SR/L3VPN, SRv6, GRE tunnels, and a files-plugin worked
example. Several topologies carry native validate: sections.
Add the sonic_clab device to the platform tables (device list, configuration
deployment, initial config, routing/dataplane/EVPN/layer-2/IPv6 module support),
a caveats section covering the image/connection model, automatic kernel-module
loading for MPLS/SR, and the SRv6 control-plane limit, a containerlab usage
section in the SONiC lab notes, and a 26.07 release note.
@roc-ops
roc-ops force-pushed the sonic-containerlab branch from 1d885ff to bf087fc Compare July 22, 2026 01:50
@roc-ops

roc-ops commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — the "reuse FRR logic and templates rather than reimplement in parallel" principle is exactly right, and I've taken it as the primary lens for this revision, not just the individually-flagged lines. Summary of what collapsed into FRR reuse vs what stays genuinely SONiC-specific, then the per-comment detail.

Reused from FRR (no parallel implementation):

  • Routing config — every routing module (bgp, ospf, isis, vrf, mpls, sr, srv6, evpn, ripv2, gateway, routing, tunnel.gre) renders through the in-tree <module>/frr.j2 templates via the inherited ansible_network_os: frr. In particular the bgp/sonic_clab.j2 template is gone — SONiC's BGP is standard FRR, so it now uses bgp/frr.j2 (docker-sonic-vs, unlike the Azure/libvirt image, does not pre-seed a BGP AS, so the no router bgp reset it carried wasn't needed).
  • Validation — collapsed the four per-module validators into a single validate/sonic_clab.py that re-exports the FRR checks (from netsim.validate.frr import *).
  • Kernel modulesclab.kmods instead of a manual modprobe.

Genuinely SONiC-specific (kept, because it isn't FRR):

  • Interface / VLAN / PortChannel objects created through SONiC's config CLI (config_db).
  • FRR daemons enabled per configured module (docker-sonic-vs ships them =no in /etc/frr/daemons).
  • Config deployed over the Ansible docker connection (docker exec) — the image has no sshd/cliconf CLI.
  • A deterministic CONFIG_DB→kernel sync for the objects the VS orchestration agents don't program (routed-port IPs, VLAN members, VXLAN).

Per comment:

kmods (initial.j2 modprobe / sonic_clab.yml kmods key): Adopted. The device now declares clab.kmods mirroring frr.yml (initial: [ 'vrf?' ]), which opts it into netlab's clab kernel-module loader; the system-wide providers.clab.kmods then auto-loads mpls_router/mpls_iptunnel (and vrf/vxlan) per module. Verified by unloading those modules from the host and booting the MPLS/L3VPN test with no manual setup — netlab loads them and the L3VPN datapath (LDP transport + BGP VPN label, real kernel label FIB) comes up 0% loss. The manual-modprobe caveat is removed.

Module init hooks (LAG PortChannel loop, VLAN creation): Moved out of the monolithic initial template into the per-module init hooks lag/sonic_clab.initial.j2 and vlan/sonic_clab.initial.j2, pulled into the initial config by extra_module_initial(['lag','vlan']) at the correct ordering point (create-before-address) — the same mechanism VyOS uses. Verified live: PortChannel + LACP up with OSPF over the aggregate, and the VLAN SVI-to-SVI datapath at 0% loss.

Reuse FRR scripting (initial.j2 interface block) and vxlan template: Assessed against the FRR templates. The routing and EVPN control planes already reuse FRR entirely (bgp/frr.j2 + zebra advertise-all-vni). What remains in these two templates is the part that genuinely isn't FRR: the FRR base initial template brings interfaces up with ip link, whereas SONiC brings them up through config_db (the config CLI), and there's no shared include to reuse for that. The vxlan template is only the kernel L2VNI/L3VNI data-plane setup that docker-sonic-vs's orchagent doesn't program from config_db; it parallels vxlan/frr.j2's kernel approach but keeps the config_db-aligned Vlan<id> bridge naming (so vrf/frr.data-plane.j2's enslave-by-ifname finds the SVI) plus idempotency guards. I've kept those as SONiC deltas and documented why; happy to converge further if you'd prefer.

vrrp.version (separate PR / buggy shutdown logic / overkill): Agreed on all three — dropped entirely from this PR. Removed the template, the test, and the gateway.vrrp.version feature flag, and reverted the extra/vrrp/version/defaults.yml registration, so this PR now touches zero files outside the sonic_clab device + docs. Base gateway/VRRP support is retained.

Rename sonic_clab.j2sonic-clab.j2 (dash): The dash is trivial for the templates, but it surfaces a latent core issue that made me hold off: netsim/cli/validate/plugin.py evaluates validate_{device}.{action}_{plugin}, and for a dash device that parses as subtraction — eval("validate_sonic-clab.show_ospf_neighbor") becomes validate_sonic - clab.show_ospf_neighbor, where clab... is a topology Box → "'Box' object is not callable", which breaks native netlab validate. No existing dash-named device (vjunos-router/-switch, vptx) ships validators, so this path has never been exercised. The device deploys and forwards fine under a dash name — only the validate eval breaks. To keep this PR free of core changes I've left it as sonic_clab for now, but I'm glad to do the rename together with a ~2-line fix that sanitizes the device name at the two build sites in that eval (str.replace('-','_')) — in this PR or a separate core PR, whichever you and Ivan prefer.

Break into smaller PRs: Agreed it's a sizable change. I'll leave the split call to you/Ivan — happy to restructure into staged PRs (e.g. device+initial first, then per-module) if that's easier to review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants