Docker alongside GNS3 on a Windows / AMD system

ยท 1015 words ยท 5 minute read

When trying to run Docker and GNS3 on the same Windows system with an AMD processor I was faced with the following challenges:

  • Docker for Desktop on Windows requires Hyper-V or WSL2, the latter in turn requires Hyper-V
  • Hyper-V on AMD processors only supports nested virtualization as of 2020 (in fact the official docs still say that “nesting is currently Intel-only”)
  • Hyper-V officially supports only Hyper-V itself for netsed virtualization, therefore rendering it useless for running GNS3

After being fed up with running GNS3 on a separate physical Linux machine I tried to come up with an actual solution to the problem. Seeing as I was already running Docker and GNS3 on an Ubuntu device I figured it should be possible to expose both of that to a Windows host through a (non Hyper-V!) VM.

Installing GNS3 in a Ubuntu VM ๐Ÿ”—

You need a working Ubuntu VM with a network interface bridged to the internet-facing interface of the host system to follow along. Seeing as there are already lots of good tutorials on virtualization and the installation of Ubuntu I won’t cover this here. Free options are for example VirtualBox or VMWare Workstation Player (assuming you use it non-commercially).

First off, make sure your VM is able to actually perform nested virtualization, as without it, this whole exercise would be quite pointless.

$ kvm-ok
INFO: /dev/kvm exists
KVM acceleration can be used

The GNS3 documentation for installing it on a Ubuntu-based distro is straight-forward. One thing I found was missing there was the daemonization of GNS3. Fortunately enough they provide a Systemd service file here. The following steps configure the GNS3 daemon:

$ cd /etc/systemd/system
$ sudo wget https://raw.githubusercontent.com/GNS3/gns3-server/master/init/gns3.service.systemd
$ sudo mv gns3.service.systemd gns3.service
$ sudo useradd gns3
$ sudo usermod -aG ubridge gns3
$ sudo usermod -aG libvirt gns3 
$ sudo usermod -aG kvm gns3
$ sudo usermod -aG wireshark gns3
$ sudo usermod -aG docker gns3
$ sudo sed -i 's/\/var\/run\/run/' gns3.service

The last line fixes the fact that /var/run symlinks to /run on newer Ubuntu distributions. Start and enable the service to run on startup and check on its status. If you the Active: active (running) then everything went well.

$ sudo systemctl start gns3
$ sudo systemctl enable gns3
$ sudo systemctl status gns3
โ— gns3.service - GNS3 server
    Loaded: loaded (/etc/systemd/system/gns3.service; enabled; vendor preset: enabled)
    Active: active (running) since Mon 2020-11-23 20:05:39 CET; 18s ago
[...]

Finally, either make sure that the host firewall is inactive or open the port in order to be able to access the GNS3 server from your Windows host:

$ sudo ufw status
 Status: inactive
# OR
$ sudo ufw allow 80

The last remaining step is to configure your GNS3 to use the server as the primary server. To check on how to do that, take a look here.

Exposing Docker in a Ubuntu VM ๐Ÿ”—

Disclaimer: The below steps expose an unauthenticated and unencrypted HTTP Docker daemon to the network. Do not do this in a production environment or really anywhere that isn’t on your home network. To find out how to secure the Docker daemon, take a look here.

If you haven’t already installed Docker during the course of the GNS3 installation, take a look at how to install Docker on a Ubuntu system here. Out of the box the Docker daemon runs locally on a Unix socket, walled of from the network. Because we want to access it from our Windows host (which is reachable only through the network interface), we need to change this. Systemd provides a mechanism for editing the service files without changing the original service file. This is neat, because it ensures that the configuration properly persists through updates of the Docker packages.

Use the following commands to automatically create a gns3.service.d directory and override.conf with a configuration that exposes the Docker daemon on port TCP/4243 of the VM (as demonstrated above make sure the firewall configuration matches this). If you want to use Docker containers within GNS3 it is vital that you keep the Unix socket configuration next to the TCP configuration, otherwise the GNS3 daemon will find itself unable to talk to the Docker daemon.

$ sudo systemctl edit docker.service /etc/systemd/system/docker.service
## Enter following content into file
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:4243

You can also verify if your changes the service file were applied properly:

$ sudo systemctl cat docker
/lib/systemd/system/docker.service
[...]
# /etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:4243

Finishing up, we need to reload the service files and then restart the service.

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

Your Docker daemon should now be up and running:

$ netstat -tpln | grep 4243
tcp6       0      0 :::4243                 :::*                    LISTEN      -

Docker configuration on the Windows host ๐Ÿ”—

If you don’t mind installing the Docker daemon (even though you won’t run it) as well as docker.exe and docker-compose.exe on your Windows system you can simply use the official installer. Otherwise you can grab the newest release from here.

In order to configure your newly aquired Docker CLI to use the remote Docker daemon, you can either set the environment variable DOCKER_HOST to the Ubuntu VM (e.g. tcp://192.168.0.100:4243), use docker -H tcp://192.178.0.100:4243 ... or use theconfiguration file. With either of these options configured properly you should now be able to use the Docker (or docker-compose) CLI from your Windows host:

PS> docker run hello-world
 Hello from Docker!
 This message shows that your installation appears to be working correctly.
 To generate this message, Docker took the following steps:
 The Docker client contacted the Docker daemon.
 The Docker daemon pulled the "hello-world" image from the Docker Hub.
 (amd64)
 The Docker daemon created a new container from that image which runs the
 executable that produces the output you are currently reading.
 The Docker daemon streamed that output to the Docker client, which sent it
 to your terminal. 
 To try something more ambitious, you can run an Ubuntu container with:
  $ docker run -it ubuntu bash
 Share images, automate workflows, and more with a free Docker ID:
  https://hub.docker.com/
 For more examples and ideas, visit:
  https://docs.docker.com/get-started/