Weaving a New Solution to Container Networking

As I described in my recent post setting up Nomad, I restored to using macvlan to get containers their own network addresses, but this had the unfortunate side effect of container being unable to talk to their host due to intricacies of how the Linux kernel does packet routing. I wasn't happy with the solution, both because containers would sometimes get stuck and not get cleaned up, resulting in more than one container with the same IP address running on the network, leading to all sorts of strange issues; and because it meant that there was a single point of failure in my router, serving both Consul DNS and hosting cloudflared for the applications which used macvlan. So I went looking for options.

Looking for a Solution

I was weary of CNIs and their support in Nomad from the many hours I spent cursing the last time I tried to deal with this issue, so I was looking for solutions that had already been successfully integrated with Nomad in the past. I landed on three options: Calico, Flannel, and Weave, thanks to a post by SUSE's Rancher project. Each had their upsides and downsides.

Calico - We use this at work, so I am somewhat familiar with its operations, but the use of BGP would cause issues as my router, a Unifi Dream Machine Pro, doesn't support BGP out of the box. It also had a bunch of enterprise features, like network policies, I do not need, so I kept looking.

Flannel - This seemed like a great option, until I got to the part where I read that it relies on an etcd cluster. While I don't have anything against etcd, I did not want to spin up another state management cluster if I didn't need to. So Flannel went in my back pocket, as a last resort.

Weave - Weave uses a proprietary routing fabric, rather than linux primitives (like iptables), it seemed like a good fit for my application, given the low barrier to entry and the simple configuration options when using it alongside Docker.

Implementing Weavenet

I used Ansible to install Weave and its associated systemd unit.

- name: Install Weave
  hosts: nomad
  tasks:
    - name: Install Weave
      uri:
        dest: /usr/local/bin/weave
        status_code: 
          - 200
          - 304
        url: https://git.io/weave
        mode: 0755
    
    - name: Create Systemd Service
      copy:
        dest: /etc/systemd/system/weave.service
        mode: 0755
        content: |
          [Unit]
          Description=Weave Network
          Documentation=http://docs.weave.works/weave/latest_release/
          Requires=docker.service
          After=docker.service
          [Service]
          EnvironmentFile=-/etc/sysconfig/weave
          ExecStartPre=/usr/local/bin/weave launch --no-restart --no-dns --ipalloc-range $IP_CIDR --metrics-addr=0.0.0.0:21049 $PEERS
          ExecStart=/usr/bin/docker attach weave
          ExecStop=/usr/local/bin/weave stop
          [Install]
          WantedBy=multi-user.target

    - name: Ensure sysconfig Directory Exists
      ansible.builtin.file:
        path: /etc/sysconfig
        state: directory

    - name: Create Weave Peers File
      copy:
        dest: /etc/sysconfig/weave
        content: |
          PEERS="10.0.10.48 10.0.10.64 10.0.10.80"
          IP_CIDR="172.30.0.0/16"

    - name: Start Weave Service
      systemd:
        state: restarted
        daemon_reload: true
        name: weave

I ended up overriding the default CIDR (10.0.0/8), as it conflicted with the parent network and explicitly listing all of the peer servers to avoid needing to join them manually.

I then ran weave expose on each of my nodes and added the /24 returned to my router's static routes table, with the target being the node that returned the IP block. This enabled clients not part of the weave network to be able to access weave clients. From there, I just needed to explicitly set the network_mode in my Nomad Job Docker Configuration and ensure that the address mode was set to driver for my service definitions.

terraform-hashi/alertmanager.hcl at main · tpaulus/terraform-hashi
Hashicorp Product (Nomad, Consul, etc.) Terraform Configurations - terraform-hashi/alertmanager.hcl at main · tpaulus/terraform-hashi
Example Configuration using WeaveNet

Hey Siri

We have a handful of Apple's HomePod Mini's across the house and my partner and I both have iPhones, so it is handy to be able to ask Siri to turn on/off the lights and what not. The actual work is done via the Home Kit Integration in Home Assistant.

One of my usual tests after I change something with either Home Assistant, or its network is to ask my office HomePod to turn on or off my Desk Lamp. Unsurprisingly, after I configured Weave on Home Assistant this stopped working. A bit of pondering lead to a potential issue. mDNS. When you pair a HomeKit device, you never put in an IP Address or other configuration, it is all handled via multicast DNS (mDNS), and now that Home Assistant was on a different subnet than the Home Pods, and iPhones, this connection was broken, despite the networks being accessible from one-another.

Conveniently this is not an uncommon problem, and it’s already been solved via tools like mDNS Reflector. Deploying a container that bridged the weave network and the server host network (which is the same one as the network our phones, HomePods, etc are connected to) instantly restored the connection between HomeKit on my phone and Home Assistant. And just like that, we were back in business.