Interconnect Incus cluster

I want to connect a few Incus cluster located at different locations and share a few services. Looking at the different network options it boils down to

  • using VXLAN on top of a VPN
  • Using OVN with remote network peering on top of VPN

I had success to setup unicast VXLAN using Wireguard to secure the communication and it works pretty well. But it comes with a few drawbacks which can be solved.
Reviewing the documentation for OVN and remote network peering it should work in a similar way but I’m not sure how to configure it correctly. Is it actually possible with the current implementation? As the announcement and documentation isn’t 100% clear about it.

Yeah, OVN interconnection peering should work fine for this assuming you have working OVN on both sides.

It’s not the easiest thing to set up, but also not the hardest, just the usual lack of OVN documentation being the issue :wink:

If you’re using TLS for your OVN deployments, you’ll first need to make sure that they’re both using the same CA as OVN interconnection currently requires that the certificates of the interconnection DBs and that of the OVN cluster be the same. As both clusters need to connect to the interconnection DBs, it effectively requires all clusters use the same CA.

Then you need to setup the extra OVN bits, so OVN IC-NB, OVN IC-SB, OVN IC processes themselves in both clusters, make sure both OVN clusters have a name defined and then mark the individual OVS instances as IC chassis.

Once that’s all done, you can add a network integration in each cluster, providing the connection details to your IC NB and SB and after that you can finally peer networks on each cluster.

Do note that this is all L3 peering, so the network on each cluster will have its own subnet and just be able to directly route to the equivalent network on the other cluster all without leaving OVN.

incus-deploy has basic support for setting up OVN IC services, this may be useful for you: incus-deploy/ansible/books/ovn.yaml at main · lxc/incus-deploy · GitHub

Otherwise the upstream documentation on OVN IC isn’t too bad either: OVN Interconnection — Open Virtual Network (OVN) 24.09.90 documentation

If you want a stretched L2 instead, this isn’t currently possible with OVN networks, but we’ve got an issue tracking this: Add support for network tunnels to OVN networks · Issue #1405 · lxc/incus · GitHub

Thanks for the useful input @stgraber.
Agree that the documentation for interconnection lacks quite some detail or useful examples I would say. My research around this topic didn’t brought up much, the only helpful one was this Hands-on with OVN Interconnection - Andreas Karis Blog.
One of the unclear things have been where does the core OVN setup stop and Incus takes over :wink: I have some experience in setup a OVN cluster and all the nity greeting pitfalls you can run into, so I was prepared to have similar issues with interconnection.
During the last days I tried to make things work but haven’t been successful. Following the provided Ansible example, reading the docs multiple times and with the help of the link above I was able to get the OVN part working at least as far as I can tell.
In my simple setup I have two nodes let’s call them “tom” and “jerry”. Each of them has a single node OVN setup. I configured all the required services of OVN and “ovn-sbctl show” list the chassis from both nodes. Created a IC-DB on tom and started the OVN-IC service on both nodes and configured the gateways. “OVN-IC-SBCTL” list both zones “az-tom” and “az-jerry” against the IC-DB.
On Incus I created an OVN network on both nodes “ovn-tom” and “ovn-jerry”, deployed an instance like in Incus docs. No real hickups, they can ping the world and internal local IP’s.
Now the fun part starts where I properly miss a step or two. On each node create a “network integration” and “network peering”
Node “tom”:

incus network integration create az-tom ovn
incus network integration set az-tom ovn.northbound_connection tcp:[<tom-ip>]:6645
incus network integration set az-tom ovn.southbound_connection tcp:[<tom-ip>]:6646

incus network peer create ovn-tom az-jerry az-tom --type=remote

Node “jerry”:

incus network integration create az-jerry ovn
incus network integration set az-jerry ovn.northbound_connection tcp:[<tom-ip>]:6645
incus network integration set az-jerry ovn.southbound_connection tcp:[<tom-ip>]:6646

incus network peer create ovn-jerry az-tom az-jerry --type=remote

As only “tom” has the IC-DB both have to use “tom-ip” otherwise it won’t work. Regardless how I configured the peering or integration a ping to any IP of the other network failed.

As mentioned there is properly a step missing or the configuration is simply somewhere wrong.

From OVN cluster network peering experience you have to create a peering from network “ovn-a” to “ovn-b” and the same from network “ovn-b” to “ovn-a”. Without doing this the peering status will show failed. In this case it is always created not much of help. What I’m kind of missing is where to define the remote network for remote peering?

Hope you can enlighten me…

What’s your ovn-ic-sbctl show and ovn-ic-nbctl show output?

That was fast :wink:

Based on the lines above this is the output:

root@tom ~ $ ovn-ic-sbctl show 
availability-zone jerry
    gateway 8d0ea0b9-f7a9-42db-bba9-f5a5236ac896
        hostname: jerry
        type: geneve
            ip: 10.8.0.2
        port ts-incus-jerry-default-ovn-jerry-jerry
            transit switch: ts-incus-jerry-default-ovn-jerry
            address: ["00:16:3e:b1:91:bc 169.254.78.65/28 fd42:9070:4a20:292a::1/64"]
availability-zone tom
    gateway 4f864677-3153-488a-9863-b4c7711206d8
        hostname: tom
        type: geneve
            ip: 10.8.0.5
        port ts-incus-tom-default-ovn-tom-tom
            transit switch: ts-incus-tom-default-ovn-tom
            address: ["00:16:3e:eb:6b:f9 169.254.200.241/28 fd42:2328:931b:7085::1/64"]
root@tom ~ $ ovn-ic-nbctl show
Transit_Switch ts-incus-tom-default-ovn-tom
Transit_Switch ts-incus-jerry-default-ovn-jerry
root@tom ~ $ 

Btw. both nodes have Incus 6.7 installed, Debian Bookworm.

Okay, so the problem is that they each came up with a different transit switch name.

For two networks to peer with each over through an IC, you need the resulting transit switch to land on the same name.

The default pattern used to determine a transit switch name is:

ts-incus-{{ integrationName }}-{{ projectName }}-{{ networkName }}

So in your case, you’ll want to make sure that:

  • The integration name is the same on both sides
  • The network name is the same on both sides

That should then result in a shared transit switch name and in the two getting peered.

Note that you can also switch how the transit switch is named to add/remove specific fields through ovn.transit.pattern. For example adding the peerName to the name then allows you to have multiple peers on the same network, setting up multiple ECMP paths between the networks.

Right, makes sense if you think about it. We are on network level and having two switch without any connection won’t work.

Changed it the way you suggested and still no joy. To make sure I haven’t messed something up I recreated the whole setup a few times but didn’t have any luck at all :frowning:

Although there is now only one switch it feels there is still something missing.

root@tom:~# ovn-ic-sbctl show 
availability-zone jerry
    gateway 9e883d2e-bace-4cc9-8a31-41bc75ecb672
        hostname: jerry
        type: geneve
            ip: 10.1.89.167
        port ts-incus-ovn-region-default-ovn-ic-jerry
            transit switch: ts-incus-ovn-region-default-ovn-ic
            address: ["00:16:3e:0f:9a:84 169.254.50.66/28 fd42:f0c5:656:8321::2/64"]
availability-zone tom
    gateway fe49c512-e599-4c52-af97-5b637d820495
        hostname: tom
        type: geneve
            ip: 10.1.89.39
        port ts-incus-ovn-region-default-ovn-ic-tom
            transit switch: ts-incus-ovn-region-default-ovn-ic
            address: ["00:16:3e:bd:8c:53 169.254.50.65/28 fd42:f0c5:656:8321::1/64"]

To keep it simple and re-produceable I created an Install script which does the full install / configuration, see link.
It just requires to VM’s tom and jerry to be created, push and execute the script on each node…

Hope this helps in nailing it down.

Okay, that looks promising, the ovn-ic-sbctl show output looks exactly as I would expect it.
Now let’s see what that looks like on the ovn-nbctl show side for both clusters.

Additionally, use incus network info to get the Logical router name on both cluster, then run ovn-nbctl lr-route-list LRNAME.

Basically we should see a port in the logical switch of each network going into the transit switch and we should see routes for the remote network show up in the logical router routing table.

Let’s start how it looks like for “tom”:

root@tom:~# incus network ls
+----------------+----------+---------+-------------+------+-------------+---------+---------+
|      NAME      |   TYPE   | MANAGED |    IPV4     | IPV6 | DESCRIPTION | USED BY |  STATE  |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| br-int         | bridge   | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| enp5s0         | physical | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| genev_sys_6081 | unknown  | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| incusbr0       | bridge   | YES     | 10.1.1.1/24 | none |             | 2       | CREATED |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| incusovn1      | bridge   | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| incusovn1a     | unknown  | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| incusovn1b     | unknown  | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| lo             | loopback | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| ovn-ic         | ovn      | YES     | 10.1.2.1/24 | none |             | 1       | CREATED |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| ovs-system     | unknown  | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+

root@tom:~# ovn-sbctl show
Chassis "61b41d6f-81bd-42ad-9f1b-032ce0945586"
    hostname: tom
    Encap geneve
        ip: "10.102.89.110"
        options: {csum="true"}
    Port_Binding incus-net2-instance-5953435b-94e7-4f77-8533-c8cdf3ef614f-eth0
    Port_Binding cr-incus-net2-lr-lrp-ext
    Port_Binding cr-ts-incus-ovn-region-default-ovn-ic
Chassis "0a1c90a3-1d9b-4221-8c0b-b2bfbb45fa11"
    hostname: jerry
    Encap geneve
        ip: "10.102.89.41"
        options: {csum="true"}
    Port_Binding ts-incus-ovn-region-default-ovn-ic-jerry

root@tom:~# ovn-nbctl show
switch 31f2a1e5-75a7-4b72-b08e-dce40169439d (incus-net2-ls-ext)
    port incus-net2-ls-ext-lsp-router
        type: router
        router-port: incus-net2-lr-lrp-ext
    port incus-net2-ls-ext-lsp-provider
        type: localnet
        addresses: ["unknown"]
switch a4ba2775-a982-4e33-88f3-c4d829ceb669 (incus-net2-ls-int)
    port incus-net2-instance-5953435b-94e7-4f77-8533-c8cdf3ef614f-eth0
        addresses: ["00:16:3e:9a:25:83 dynamic"]
    port incus-net2-ls-int-lsp-router
        type: router
        router-port: incus-net2-lr-lrp-int
switch d0a7d1fb-0391-4be9-ae75-0f724fd23f2a (ts-incus-ovn-region-default-ovn-ic)
    port ts-incus-ovn-region-default-ovn-ic-jerry
        type: remote
        addresses: ["00:16:3e:5e:bc:8d 169.254.88.194/28 fd42:4362:a79:80a8::2/64"]
    port ts-incus-ovn-region-default-ovn-ic-tom
        type: router
        router-port: ts-incus-ovn-region-default-ovn-ic
router 55d635bf-4896-4691-8493-732816ffdb10 (incus-net2-lr)
    port incus-net2-lr-lrp-ext
        mac: "00:16:3e:ea:2d:3e"
        networks: ["10.1.1.5/24"]
    port incus-net2-lr-lrp-int
        mac: "00:16:3e:ea:2d:3e"
        networks: ["10.1.2.1/24"]
    port ts-incus-ovn-region-default-ovn-ic
        mac: "00:16:3e:ea:2d:3e"
        networks: ["169.254.88.193/28", "fd42:4362:a79:80a8::1/64"]
    nat a1afcb17-284b-474f-9145-d2391301e736
        external ip: "10.1.1.5"
        logical ip: "10.1.2.0/24"
        type: "snat"

root@tom:~# incus network info ovn-ic
Name: ovn-ic
MAC address: 00:16:3e:ea:2d:3e
MTU: 1442
State: up
Type: broadcast

IP addresses:
  inet	10.1.2.1/24 (link)

Network usage:
  Bytes received: 0B
  Bytes sent: 0B
  Packets received: 0
  Packets sent: 0

OVN:
  Chassis: tom
  Logical router: incus-net2-lr

root@tom:~#  ovn-nbctl lr-route-list incus-net2-lr
IPv4 Routes
Route Table <main>:
                0.0.0.0/0                  10.1.1.1 dst-ip incus-net2-lr-lrp-ext

Now here is “jerry” view:

root@jerry:~# incus network ls
+----------------+----------+---------+-------------+------+-------------+---------+---------+
|      NAME      |   TYPE   | MANAGED |    IPV4     | IPV6 | DESCRIPTION | USED BY |  STATE  |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| br-int         | bridge   | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| enp5s0         | physical | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| genev_sys_6081 | unknown  | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| incusbr0       | bridge   | YES     | 10.2.1.1/24 | none |             | 2       | CREATED |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| incusovn1      | bridge   | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| incusovn1a     | unknown  | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| incusovn1b     | unknown  | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| lo             | loopback | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| ovn-ic         | ovn      | YES     | 10.2.2.1/24 | none |             | 1       | CREATED |
+----------------+----------+---------+-------------+------+-------------+---------+---------+
| ovs-system     | unknown  | NO      |             |      |             | 0       |         |
+----------------+----------+---------+-------------+------+-------------+---------+---------+

root@jerry:~# ovn-sbctl show
Chassis "0a1c90a3-1d9b-4221-8c0b-b2bfbb45fa11"
    hostname: jerry
    Encap geneve
        ip: "10.102.89.41"
        options: {csum="true"}
    Port_Binding incus-net2-instance-455f260b-1855-406e-8aee-f731403d00fc-eth0
    Port_Binding cr-ts-incus-ovn-region-default-ovn-ic
    Port_Binding cr-incus-net2-lr-lrp-ext
Chassis "61b41d6f-81bd-42ad-9f1b-032ce0945586"
    hostname: tom
    Encap geneve
        ip: "10.102.89.110"
        options: {csum="true"}
    Port_Binding ts-incus-ovn-region-default-ovn-ic-tom
root@jerry:~# ovn-nbctl show
switch 2fac9bf1-35ba-4890-a75e-08b39c452b9f (incus-net2-ls-int)
    port incus-net2-instance-455f260b-1855-406e-8aee-f731403d00fc-eth0
        addresses: ["00:16:3e:b2:8d:c7 dynamic"]
    port incus-net2-ls-int-lsp-router
        type: router
        router-port: incus-net2-lr-lrp-int
switch c3d384d2-7ea3-46bd-b1ca-c316b3a95e67 (incus-net2-ls-ext)
    port incus-net2-ls-ext-lsp-provider
        type: localnet
        addresses: ["unknown"]
    port incus-net2-ls-ext-lsp-router
        type: router
        router-port: incus-net2-lr-lrp-ext
switch b42de947-8a8c-42a7-9d1f-b5ef743bd3bf (ts-incus-ovn-region-default-ovn-ic)
    port ts-incus-ovn-region-default-ovn-ic-tom
        type: remote
        addresses: ["00:16:3e:ea:2d:3e 169.254.88.193/28 fd42:4362:a79:80a8::1/64"]
    port ts-incus-ovn-region-default-ovn-ic-jerry
        type: router
        router-port: ts-incus-ovn-region-default-ovn-ic
router 454817e8-8488-41b1-9dc1-38060bde5ee8 (incus-net2-lr)
    port ts-incus-ovn-region-default-ovn-ic
        mac: "00:16:3e:5e:bc:8d"
        networks: ["169.254.88.194/28", "fd42:4362:a79:80a8::2/64"]
    port incus-net2-lr-lrp-int
        mac: "00:16:3e:5e:bc:8d"
        networks: ["10.2.2.1/24"]
    port incus-net2-lr-lrp-ext
        mac: "00:16:3e:5e:bc:8d"
        networks: ["10.2.1.5/24"]
    nat f5327eca-9b22-4e45-b72f-96009574ba66
        external ip: "10.2.1.5"
        logical ip: "10.2.2.0/24"
        type: "snat"

root@jerry:~# incus network info ovn-ic
Name: ovn-ic
MAC address: 00:16:3e:5e:bc:8d
MTU: 1442
State: up
Type: broadcast

IP addresses:
  inet	10.2.2.1/24 (link)

Network usage:
  Bytes received: 0B
  Bytes sent: 0B
  Packets received: 0
  Packets sent: 0

OVN:
  Chassis: jerry
  Logical router: incus-net2-lr
root@jerry:~# ovn-nbctl lr-route-list incus-net2-lr
IPv4 Routes
Route Table <main>:
                0.0.0.0/0                  10.2.1.1 dst-ip incus-net2-lr-lrp-ext

Reading the details it seems like all networks and their routing are there? Both sides have the networks listed but a ping between the network still fails…

Btw. did you look at the script (link) I added if there is anthing poping up that would explain this?

I only looked at it now.

The thing that jumps out is that I’m not seeing any OVN configuration for route advertisement, without that, the networks wouldn’t be able to talk to each other without you loading manual routes into OVN.

Try this on both sides and see if that solves it:

ovn-nbctl set NB_Global . options:ic-route-adv=true options:ic-route-learn=true
1 Like

Thanks @stgraber,

this was the missing piece of information. Thought that I copied it from the Ansible example you linked above. So sending the exact steps and having a second pair of eyes reviewing was definitely the right approach.

Now tom can ping jerry and the other way around:

root@jerry:~# incus shell ovn1
ovn1:~# ping 10.1.2.2
PING 10.1.2.2 (10.1.2.2): 56 data bytes
64 bytes from 10.1.2.2: seq=0 ttl=62 time=1.687 ms
64 bytes from 10.1.2.2: seq=1 ttl=62 time=0.246 ms
64 bytes from 10.1.2.2: seq=2 ttl=62 time=0.290 ms
64 bytes from 10.1.2.2: seq=3 ttl=62 time=0.370 ms
^C
--- 10.1.2.2 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.246/0.648/1.687 ms
ovn1:~# ping 10.1.2.1
PING 10.1.2.1 (10.1.2.1): 56 data bytes
64 bytes from 10.1.2.1: seq=0 ttl=253 time=0.810 ms
64 bytes from 10.1.2.1: seq=1 ttl=253 time=0.976 ms
^C
--- 10.1.2.1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.810/0.893/0.976 ms
ovn1:~# ping 10.1.1.5
PING 10.1.1.5 (10.1.1.5): 56 data bytes
64 bytes from 10.1.1.5: seq=0 ttl=253 time=0.586 ms
64 bytes from 10.1.1.5: seq=1 ttl=253 time=0.613 ms
64 bytes from 10.1.1.5: seq=2 ttl=253 time=0.542 ms
^C
--- 10.1.1.5 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.542/0.580/0.613 ms
ovn1:~# 
root@tom:~# incus shell ovn1
ovn1:~# ping 10.2.2.2
PING 10.2.2.2 (10.2.2.2): 56 data bytes
64 bytes from 10.2.2.2: seq=0 ttl=62 time=1.233 ms
64 bytes from 10.2.2.2: seq=1 ttl=62 time=0.338 ms
^C
--- 10.2.2.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.338/0.785/1.233 ms
ovn1:~# ping 10.2.2.1
PING 10.2.2.1 (10.2.2.1): 56 data bytes
64 bytes from 10.2.2.1: seq=0 ttl=253 time=0.716 ms
64 bytes from 10.2.2.1: seq=1 ttl=253 time=0.525 ms
^C
--- 10.2.2.1 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.525/0.620/0.716 ms
ovn1:~# ping 10.2.1.5
PING 10.2.1.5 (10.2.1.5): 56 data bytes
64 bytes from 10.2.1.5: seq=0 ttl=253 time=0.880 ms
64 bytes from 10.2.1.5: seq=1 ttl=253 time=0.459 ms
^C
--- 10.2.1.5 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.459/0.669/0.880 ms
ovn1:~# 

That makes my day!!!
Now it only requires to fix some of the Ansible bugs I discovered (but that is a different story) and get it deployed.

Really appreciate your help and all the hard work you put into the project.

Btw. 6.8 solves quiet some issues I was facing.