It has been almost 7 months since the last post: Containerlab, the future of your virtual network lab (Part 2). A new year has started as well, so before we get into the topic, I would like to say
Happy new year to all our readers!
Going back to the topic. In the last two posts, we talked about what Containerlab is, and how to get a basic network mapped and working.
The problem with our network setup from part 2
While the initial setup we had on part 2 of this series gives us a working setup, it has some pitfalls, for example:
- Once we stop our lab, configurations are lost for all nodes
- We would have to reconfigure all our nodes every time we start the lab
Solving config issues
Fortunately, we can go around the issues expressed above, and make our nodes apply configurations after they boot up. This will allow us to simplify the deployment, and spend less time performing configuration changes and setup
Folder structure
This is our folder structure for this lab:
.
├── configs
│ ├── ceos1.cfg
│ ├── ceos2.cfg
│ ├── init_centos1.sh
│ └── init_centos2.sh
└── lab01.clab.yml
Now, let us explore the content of each file inside configs:
ceos1.cfg:
vlan internal order descending range 3000 4094
!
hostname ceos1-jtech
!
spanning-tree mode none
!
no aaa root
!
username autoadmin privilege 15 role network-admin secret sha512 $6$oyqPzrhnOWEv/vTa$9Aiol0gBc.O/C0MXmP2mKEqqv5u2eRc4jRXNTZ/44Dhi8ZTRmV3kgzKJgoMo27V/RqwlL5l1SU0o41JrXFC0d1
!
vrf instance mgmtVrf
!
ip routing
ip routing vrf mgmtVrf
!
ipv6 unicast-routing
ipv6 unicast-routing vrf mgmtVrf
!
vlan 10
name servers
!
vlan 20
name clients
!
interface Loopback0
description C: cEOS1-Loopback0
ip address 1.1.1.1/32
ipv6 address 2001:db8::1:1:1:1/128
!
interface ethernet1
description L: cEOS2-Eth1
no switchport
load-interval 30
ip address 10.10.10.0/31
ipv6 address 2001:db8:100::0/127
ip ospf area 0
ipv6 ospf 1 area 0
!
interface ethernet2
description L: centos1
load-interval 30
switchport
switchport mode access
switchport access vlan 10
!
interface Management0
description L: Mgmt Interface
vrf mgmtVrf
ip address 10.10.10.2/24
ipv6 address 2001:10:10:10::2/64
!
interface vlan 10
description H: Servers vlan
load-interval 30
ip address 172.16.10.1/24
ipv6 address 2001:db8:172:16:10::1/64
!
router ospf 1
router-id 1.1.1.1
redistribute connected
redistribute static
log-adjacency-changes details
bfd default
!
ipv6 router ospf 1
router-id 1.1.1.1
redistribute static
redisttibute connected
log-adjacency-changes details
bfd default
!
router bfd
interval 500 min-rx 500 multiplier 3 default
!
management api http-commands
no shutdown
!
management api gnmi
transport grpc default
!
management api netconf
transport ssh default
!
ceos2.cfg
vlan internal order descending range 3000 4094
!
hostname ceos2-jtech
!
spanning-tree mode none
!
no aaa root
!
username autoadmin privilege 15 role network-admin secret sha512 $6$oyqPzrhnOWEv/vTa$9Aiol0gBc.O/C0MXmP2mKEqqv5u2eRc4jRXNTZ/44Dhi8ZTRmV3kgzKJgoMo27V/RqwlL5l1SU0o41JrXFC0d1
!
vrf instance mgmtVrf
!
ip routing
ip routing vrf mgmtVrf
!
ipv6 unicast-routing
ipv6 unicast-routing vrf mgmtVrf
!
vlan 10
name servers
!
vlan 20
name clients
!
interface Loopback0
description C: cEOS2-Loopback0
ip address 2.2.2.2/32
ipv6 address 2001:db8::2:2:2:2/128
!
interface ethernet1
description L: cEOS2-Eth1
no switchport
load-interval 30
ip address 10.10.10.1/31
ipv6 address 2001:db8:100::1/127
ip ospf area 0
ipv6 ospf 1 area 0
!
interface ethernet2
description L: centos1
load-interval 30
switchport
switchport mode access
switchport access vlan 10
!
interface Management0
description L: Mgmt Interface
vrf mgmtVrf
ip address 10.10.10.3/24
ipv6 address 2001:10:10:10::3/64
!
interface vlan 10
description H: Servers vlan
load-interval 30
ip address 192.168.10.1/24
ipv6 address 2001:db8:192:168:10::1/64
!
router ospf 1
router-id 2.2.2.2
redistribute connected
redistribute static
log-adjacency-changes details
bfd default
!
ipv6 router ospf 1
router-id 2.2.2.2
redistribute static
redisttibute connected
log-adjacency-changes details
bfd default
!
router bfd
interval 500 min-rx 500 multiplier 3 default
!
management api http-commands
no shutdown
!
management api gnmi
transport grpc default
!
management api netconf
transport ssh default
!
init_centos1.sh
#!/bin/bash
ip address add 172.16.10.2/24 dev eth1
ip address add 2001:db8:172:16:10::2/64 dev eth1
ip route del default
ip route add 172.16.0.0/12 via 172.16.10.1 dev eth1
ip route add default via 172.16.10.1
ip route add 2001:db8::/32 via 2001:db8:172:16:10::1 dev eth1
init_centos2.sh
#!/bin/bash
ip address add 192.168.10.2/24 dev eth1
ip address add 2001:db8:192:168:10::2/64 dev eth1
ip route del default
ip route add 192.168.0.0/16 via 192.168.10.1 dev eth1
ip route add default via 192.168.10.1
ip route add 2001:db8::/32 via 2001:db8:192:168:10::1 dev eth1
As we can see these are very basic configs for the Arista and Centos nodes. Which allows us to have the nodes communicate with each other.
Adding the configs to our lab network setup
Now that we have the configurations added to our configs folder, we need to specify the actions on the container lab topology file (lab01.clab.yml)
For nodes that are like Arista, Nokia, Juniper, etc, we will use the startup-config keyword and pass the file path where the config is located, in this case, configs/ceos1.cfg
Now our node definition on the lab01.clab.yml will look like this:
topology:
nodes:
ceos1:
kind: ceos
image: ceos:4.25.4M
startup-config: ./configs/ceos1.cfg
mgmt_ipv4: 10.10.10.2
mgmt_ipv6: 2001:10:10:10::2
You can repeat this process for the other nodes if you want to apply the configuration to
Adding and running scripts on Linux nodes
Although the startup-config loads any configuration we need for our networking nodes, applying configuration to Linux nodes is a bit different. For that reason, we need to take a different approach as to how we need to configure it.
If we look back at our lab topology, we will see that we have 2 client nodes running centos 8

Now, based on our topology, and what we want to achieve of the 2 centos devices being able to ping each other while on different subnets, we need to implement some basic IP configuration as well as routes.
The problem with the centos devices is the same as we have with the cEOS devices, where every time we destroy our lab the configuration is removed and we would have to add it again.
To solve this issue we created a bash script for each node that configures the IPv4 and IPv6 addresses as well as routes.
To be able to accomplish this, we need to add some changes to our node definition on the lab topology file
centos1:
kind: linux
image: centos:8
binds:
- ./configs/init_centos1.sh:/init_centos1.sh
exec:
- bash /init_centos1.sh
mgmt_ipv4: 10.10.10.4
mgmt_ipv6: 2001:10:10:10::4
So, if we look at the configuration here, what we are doing is using the binds: keyword to map our local directory and file to a specific directory and file on our node.
This is like the volume keyword in Docker.
The binds keyword take a list of bindings, allowing multiple files or folders to be shared between your local machine to your virtual node
After that, we will use the exec: keyword to specify a list of commands that we want the node to run after startup.
This is similar to the command keyword on Docker
As the binds keyword, the exec keyword can take a list of commands to be run after startup
Running our lab after the changes
Now with all these changes in place, we can start to run our lab again. At this time, when we run the lab though, a full configuration will be in place on our nodes.
we can verify this by entering the container bash and checking if we can ping between devices
We can see the results here:
docker exec -it clab-lab01-centos1 bash
[root@centos1 /]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
111: eth0@if112: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:0a:0a:0a:04 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.10.10.4/24 brd 10.10.10.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 2001:10:10:10::4/64 scope global nodad
valid_lft forever preferred_lft forever
inet6 fe80::42:aff:fe0a:a04/64 scope link
valid_lft forever preferred_lft forever
119: eth1@if120: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9500 qdisc noqueue state UP group default
link/ether aa:c1:ab:59:59:6d brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 172.16.10.2/24 scope global eth1
valid_lft forever preferred_lft forever
inet6 2001:db8:172:16:a8c1:abff:fe59:596d/64 scope global dynamic mngtmpaddr
valid_lft 2591944sec preferred_lft 604744sec
inet6 2001:db8:172:16:10::2/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::a8c1:abff:fe59:596d/64 scope link
valid_lft forever preferred_lft forever
[root@centos1 /]# ip route
default via 172.16.10.1 dev eth1
10.10.10.0/24 dev eth0 proto kernel scope link src 10.10.10.4
172.16.0.0/12 via 172.16.10.1 dev eth1
172.16.10.0/24 dev eth1 proto kernel scope link src 172.16.10.2
[root@centos1 /]# ip -6 route
2001:10:10:10::/64 dev eth0 proto kernel metric 256 pref medium
2001:db8:172:16::/64 dev eth1 proto kernel metric 256 pref medium
2001:db8::/32 via 2001:db8:172:16:10::1 dev eth1 metric 1024 pref medium
fe80::/64 dev eth0 proto kernel metric 256 pref medium
fe80::/64 dev eth1 proto kernel metric 256 pref medium
default via 2001:10:10:10::1 dev eth0 metric 1024 pref medium
default via fe80::21c:73ff:fee3:6760 dev eth1 proto ra metric 1024 expires 1736sec mtu 1500 hoplimit 64 pref medium
[root@centos1 /]# ping 172.16.10.1
PING 172.16.10.1 (172.16.10.1) 56(84) bytes of data.
64 bytes from 172.16.10.1: icmp_seq=1 ttl=64 time=1.97 ms
64 bytes from 172.16.10.1: icmp_seq=2 ttl=64 time=2.10 ms
^C
--- 172.16.10.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 2ms
rtt min/avg/max/mdev = 1.972/2.035/2.098/0.063 ms
[root@centos1 /]# ping 192.168.10.2
PING 192.168.10.2 (192.168.10.2) 56(84) bytes of data.
64 bytes from 192.168.10.2: icmp_seq=1 ttl=62 time=6.71 ms
64 bytes from 192.168.10.2: icmp_seq=2 ttl=62 time=4.42 ms
64 bytes from 192.168.10.2: icmp_seq=3 ttl=62 time=4.70 ms
^C
--- 192.168.10.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 6ms
rtt min/avg/max/mdev = 4.417/5.274/6.712/1.026 ms
Where to go from here
Now that you have most of the bases of Containerlab you can go over to their official site and documentation and explore other network Operative Systems that you can use with Containerlab
There are also multiple examples of lab examples that you can run with a more complicated topology than the example here
If you are interested in this lab example and code, you can find all the files on my GitHub repository