Compose: The order in which networks are assigned to interfaces does not match the order they are listed in compose file

Created on 21 Mar 2017  路  15Comments  路  Source: docker/compose

The following report was produced using Docker version 17.03.0-ce and compose version docker-compose version 1.11.2, build dfed245.

Given a docker-compose.yml that looks like this:

version: "2"

services:
  server:
    image: alpine
    command: sleep 999
    networks:
      - nw0
      - nw1
      - nw2
      - nw3

networks:
  nw0:
  nw1:
  nw2:
  nw3:

I would expect the networks to be assigned to interface in order, such that eth0 is attached to nw0, eth1 is attached to nw1, etc. This is exactly the behavior I see if I start a container using docker run and then attach additional networks with docker network connect.

However, when using docker-compose and the above compose file, the ordering of networks and interfaces appears to be inconsistent. Assuming that the above compose file is in a directory named nwtest, this script will demonstrate the problem:

#!/bin/sh

docker-compose up -d

for nw in 0 1 2 3; do
    nw_cidr=$(docker network inspect -f '{{ (index .IPAM.Config 0).Subnet }}' \
        nwtest_nw${nw})
    if_cidr=$(docker exec -it nwtest_server_1 ip addr show eth${nw} |
        awk '$1 == "inet" {print $2}')

    nw_net=$(ipcalc -n $nw_cidr | cut -f2 -d=)
    if_net=$(ipcalc -n $if_cidr | cut -f2 -d=)

    echo "nw${nw} $nw_net eth${nw} ${if_net}"

    if [ "$if_net" != "$nw_net" ]; then
        echo "MISMATCH: nw${nw} = $nw_net, eth${nw} = $if_net" >&2
    fi
done

docker-compose stop

On my system, that produces as output:

Starting nwtest_server_1
nw0 192.168.32.0 eth0 192.168.32.0
nw1 192.168.48.0 eth1 192.168.48.0
nw2 192.168.64.0 eth2 192.168.80.0
MISMATCH: nw2 = 192.168.64.0, eth2 = 192.168.80.0
nw3 192.168.80.0 eth3 192.168.64.0
MISMATCH: nw3 = 192.168.80.0, eth3 = 192.168.64.0
Stopping nwtest_server_1 ... done

For comparison, here is a script that performs the same test using docker run and manual attachment:

#!/bin/sh

docker rm -f nwtest_server_1
docker run -d --name nwtest_server_1 --network nwtest_nw0 \
    alpine sleep 999

for nw in 1 2 3; do
    docker network connect nwtest_nw${nw} nwtest_server_1
done

for nw in 0 1 2 3; do
    nw_cidr=$(docker network inspect -f '{{ (index .IPAM.Config 0).Subnet }}' \
        nwtest_nw${nw})
    if_cidr=$(docker exec -it nwtest_server_1 ip addr show eth${nw} |
        awk '$1 == "inet" {print $2}')

    nw_net=$(ipcalc -n $nw_cidr | cut -f2 -d=)
    if_net=$(ipcalc -n $if_cidr | cut -f2 -d=)

    echo "nw${nw} $nw_net eth${nw} ${if_net}"

    if [ "$if_net" != "$nw_net" ]; then
        echo "MISMATCH: nw${nw} = $nw_net, eth${nw} = $if_net" >&2
    fi
done

docker rm -f nwtest_server_1

This always runs without error.

Most helpful comment

+1 form me as well.
Editing my response to add some details. I thought about what @johnharris85 mentioned, so I numbered my network names in the order I want them to be connected and I can get around the issue for now.

For example part of my compose looks like this:
networks:
-- 1firstnw
-- 2nw
-- 3nw

Hope this helps someone.

All 15 comments

Initial guess would be networks is implemented as a dict. Will take a look this evening.

Thanks @johnharris85
There is also an open bounty at SO (link): , where you can also add your answer.

Same issue. Gets tricky when you have to rely on the order e.g. for iptables rules like "iptables -t nat -A POSTROUTING -o eth3 -j MASQUERADE"

+1. Trying to spin up some router topologies using docker-compose. This completely screws up the connections for the protocols.

+1 form me as well.
Editing my response to add some details. I thought about what @johnharris85 mentioned, so I numbered my network names in the order I want them to be connected and I can get around the issue for now.

For example part of my compose looks like this:
networks:
-- 1firstnw
-- 2nw
-- 3nw

Hope this helps someone.

+1

Workaround of setting alphabetically order is not working for me in docker-compose version 1.13.0, build 1719ceb, it seems to be adding ifaces randomly.

In any case, even if alphabetically works, I still need to have the same network assigned to different interfaces in different containers, so still need to force the ordering.

As @johnharris85 mentioned, network configuration is passed to docker engine as an unordered dict if networks are created with the container. One possible implementation would be to first create the container and then connect the container to each network one by one. This behaviour could be also configurable.

I have been playing with the scripts provided by @larsks , and docker engine keeps the order of the interfaces only when networks are connected to a running container. Otherwise, it will also create the interfaces randomly.

If the container is started after connecting the networks, it will also have mismatches

docker rm -f nwtest_server_1
docker create --name nwtest_server_1 --network nwtest_nw0 \
    alpine sleep 999

for nw in 1 2 3 4 5 6 7 8 9; do
    docker network connect nwtest_nw${nw} nwtest_server_1
done

docker start nwtest_server_1

Same happens if container is restarted

docker rm -f nwtest_server_1
docker run -d --name nwtest_server_1 --network nwtest_nw0 \
    alpine sleep 999

for nw in 1 2 3 4 5 6 7 8 9; do
    docker network connect nwtest_nw${nw} nwtest_server_1
done

docker stop nwtest_server_1
docker start nwtest_server_1

Anyway, even if docker engine offers support for this behaviour (this is already adressed: https://github.com/moby/moby/issues/25181), docker-compose won't respect the definition order since original network list is converted to an unordered list.

In addition, if connections include parameters like a fixed ip address, networks is not a list at all, it is implemented as a dictionary. Maybe some additional parameter like order or priority would be needed if docker engine implements this behaviour at some point.

I thought I had an easy fix for this, but it turns out docker network connect plays its own game, as @LuisPiedra pointed out correctly. It is not enough to sort the calls to connect_container_to_network() ...

+1, any progress on this yet?

Issue should be fixed by #5566 - at least the Compose aspect of it.

@shin can you elaborate what exactly is the fix? there werent much details on the link

extending @larsks example I've created this gist that demonstrate that ordering is dict-based and not yaml-based.

The gist include everything you need to run the test. Just chmod+x it.

@shin is this working on docker swarm?

https://docs.docker.com/compose/compose-file/compose-file-v2/#priority

Swarm features should be requested on the Swarm repo or Moby repo.

@shin it seems that priority is missing in version 3, so my guess is that is deprecated.

@gentunian after looking at the code, I don't see any difference in handling network priorities between v2 and v3.
See https://github.com/docker/compose/blob/master/compose/network.py#L325, and PR docker/compose#5566 clearly show that v3 is ok since at least jan 2018...

however, a rapid test shows that priorities are currently ignored by docker, would you choose v2 or v3... lexical order of network names seems to be overriding the priorities you set. see https://gist.github.com/jfellus/cfee9efc1e8e1baf9d15314f16a46eca

(the answer to this may also answer https://github.com/docker/cli/issues/1372)

Was this page helpful?
0 / 5 - 0 ratings