Docker entschlüsselt: Netzwerk Teil 2
Im ersten Teil
von “Docker entschlüsselt: Netzwerk” haben wir gesehen,
wie der Docker-Daemon Netzwerkinterfaces, die docker0
-Bridge und die
Kommunikation der Container nach außen und untereinander managed.
Im zweiten Teil sollen nun die Grundlagen geschaffen werden, damit Docker-Container
auch über Host-Grenzen hinweg kommunizieren können. Dafür gibt es mehrere
Möglichkeiten (z.B. die Default-Bridge docker0
an ein externes Interface anzuschließen oder
das iptables-Regelwerk zu erweitern), wir wählen diejenige, welche mit der Standardkonfiguration
des Docker-Daemon funktioniert.
Der Plan: Eine neue Bridge anlegen
Ziel ist es, auf zwei virtuellen Maschinen je einen Docker Container zu instanziieren.
Dieser Container wird mit einem neuen eth1
Netzwerkinterface versorgt, das über
eine eigene br1
Netzwerk-Bridge mit einem eth1
Netzwerkinterface des Hosts verbunden ist:
Voraussetzungen
Um ein solches Setup schnell aufzusetzen, empfiehlt sich die Kombination aus Vagrant und Virtualbox. Als Basis kann dafür unser DockerBox - Projekt auf github dienen. Dazu das passende Vagrantfile für zwei VMs auf Basis Ubuntu:
Vagrant.configure("2") do |config|
# https://vagrantcloud.com/stamm/trusty64-dockeattr_reader :attr_names
config.vm.box = "trusty64"
config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
config.vm.define "docker-test1", primary: true do |s|
s.vm.network "private_network", ip: "192.168.77.5"
s.vm.provider "virtualbox" do |vb|
vb.customize [ 'modifyvm', :id, '--nicpromisc2', 'allow-all']
vb.gui = false
vb.customize [ "modifyvm", :id, "--memory", "512"]
vb.customize [ "modifyvm", :id, "--cpus", "1"]
end
end
config.vm.define "docker-test2", primary: true do |s|
s.vm.network "private_network", ip: "192.168.77.6"
s.vm.provider "virtualbox" do |vb|
vb.customize [ 'modifyvm', :id, '--nicpromisc2', 'allow-all']
vb.gui = false
vb.customize [ "modifyvm", :id, "--memory", "512"]
vb.customize [ "modifyvm", :id, "--cpus", "1"]
end
end
end
Das besondere liegt in der Definition eines zusätzlichen Netzwerkinterfaces
eth1
, dass im weiteren in den Promisc-Mode geschaltet wird:
s.vm.network "private_network", ip: "192.168.77.5"
s.vm.provider "virtualbox" do |vb|
vb.customize [ 'modifyvm', :id, '--nicpromisc2', 'allow-all']
Auf der Seite des Hosts wird dazu eine Host-Only Bridge erzeugt (z.B. vnet1
),
an dem diese virtuellen Netzwerkinterfaces angeklemmt sind.
Nach Starten der VMs kann man sich das Ergebnis anschauen:
$ vagrant up
....
$ vagrant ssh docker-test1
....
$ ip addr show
...
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master ovs-system state UP group default qlen 1000
link/ether 08:00:27:a5:83:64 brd ff:ff:ff:ff:ff:ff
inet 192.168.77.5/24 brd 192.168.77.255 scope global eth1
valid_lft forever preferred_lft forever
In den beiden VMs werden nun noch Pakete, u.a. docker, nach installiert:
$ sudo apt-get -y update
$ sudo apt-get -y install curl git openssl ca-certificates make bridge-utils arping
$ sudo apt-get install -y docker.io
$ sudo ln -sf /usr/bin/docker.io /usr/local/bin/docker
Alternativ kann die DockerBox eingesetzt werden, dort muss nur das Paket bridge-utils nachinstalliert werden.
Um mit dem Docker-Containern zu experimentieren, ziehen wir das Ubuntu-Image:
$ sudo -i
~# docker pull ubuntu:latest
Und instanziieren einen neuen Docker-Container, lassen ihn im Vordergrund geöffnet.
~# docker run -t -i ubuntu:latest /bin/bash
Um das Ziel zu erreichen, benötigt jeder Container ein neues Netzwerkinterface. Außerdem soll auf den VMs eine neue Bridge existieren, die an das VM-Interface mit dem privaten Netzwerk angeschlossen ist.
Den größten Teil dieser Arbeit kann dabei Pipework übernehmen.
Pipework
Bei pipework handelt es sich um ein Shell-Skript, das sich um genau diese Aufgaben kümmert:
- Anlegen einer Bridge auf dem Host
- Anlegen eines Netzwerkinterfaces im Container, zugeordnet zu dessen Namenspace
- Anlegen eines (Peer-)Netzwerkinterfaces auf dem Host, verknüpft zum Interface im Container
- Anklemmen des Host-Interfaces an die Bridge
Dabei versteht es sich mit der Linux Bridge und Open vSwitch und bietet weitreichende Möglichkeiten.
Auf den VMs kann pipework folgendermassen installiert werden:
$ sudo -i
~# git clone https://github.com/jpetazzo/pipework
~# cd pipework
Um einen Container mit einem neuen Netzwerkinterface zu versorgen, brauchen wir jeweils seine Container ID.
~# docker ps
....
~# CID=<Container-ID einsetzen>
Jetzt geben wir dem Container ein neues Interface, mit einer IP-Adresse
~# ./pipework br0 $CID 192.168.77.10/24
# bzw. auf der zweiten VM:
~# ./pipework br0 $CID 192.168.77.20/24
In der (noch offenen, s.o.) Container-Shell lässt sich das nachprüfen:
$ ip addr show eth1
20: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether be:fc:f1:47:02:2a brd ff:ff:ff:ff:ff:ff
inet 192.168.77.10/24 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::bcfc:f1ff:fe47:22a/64 scope link
valid_lft forever preferred_lft forever
D.h. pipework hat uns ein passendes Interfaces erzeugt und mit einer IP versorgt. Auf dem Host lässt sich der Zustand der Bridge anzeigen:
~# brctl show
bridge name bridge id STP enabled interfaces
br0 8000.0800273bcbbb no pl5330eth1
Es ist zu sehen, dass auf der docker0
-Bridge ein veth-Interface angebunden ist
(im Container: eth0), und auf der neuen br0
-Bridge ein anderes virtuellees-Interface,
das im Container dem neuen eth1 entspricht. Pipework vergibt dabei Interface-Namen, die
mit “pl” prefixed sind.
Anzeige der Bridge-/Interface-Struktur
Mit einem Ruby-Skript lässt sich der Zusammenhang zwischen Bridges, Interfaces auf dem Host und in den Container anzeigen:
~# git clone https://github.com/aschmidt75/docker-network-inspect
~# cd docker-network-inspect/lib/
~# ./docker-network-inspect.rb $CID
CONTAINER 6437709a4ea2
+ PID 5330
+ INTERFACES
+ lo (1)
+ eth0 (5)
+ HOST PEER veth6166 (6)
+ BRIDGE
+ eth1 (8)
+ HOST PEER pl5330eth1 (9)
+ BRIDGE br0
Container über VM-Grenzen verbinden
Um die Container auf den beiden VMs miteinander sprechen zu lassen, wird eine
Verbindung der beiden neuen Bridges notwendig. Dazu liegen auf den VMs die
Host-Interfaces (eth1
) bereit. Wichtig ist, dass diese Interfaces und die
Interfaces der Container im selben Subnetz liegen (hier: 192.168.77.0/24)
In den VMs verbinden wir das jeweilige eth1
mit der Bridge br0
~# brctl addif br0 eth1
~# brctl show br0
bridge name bridge id STP enabled interfaces
br0 8000.0800273bcbbb no eth1
pl5330eth1
Im Container selber lässt sich nun die IP des jeweils anderen Docker-Containers auf der anderen VM anpingen:
- Tipp: Auf die richtige IP in der jeweiligen VM achten!
Im Docker-Container auf der docker-test1
-VM hilft folgender Test:
~# ping 192.168.77.20
PING 192.168.77.20 (192.168.77.20) 56(84) bytes of data.
64 bytes from 192.168.77.20: icmp_seq=1 ttl=64 time=0.364 ms
64 bytes from 192.168.77.20: icmp_seq=2 ttl=64 time=0.524 ms
^C
--- 192.168.77.20 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.364/0.444/0.524/0.080 ms
Im Docker-Container auf der docker-test2
-VM hilft folgender Test:
~# ping 192.168.77.10
PING 192.168.77.10 (192.168.77.10) 56(84) bytes of data.
64 bytes from 192.168.77.10: icmp_seq=1 ttl=64 time=0.401 ms
64 bytes from 192.168.77.10: icmp_seq=2 ttl=64 time=0.675 ms
^C
--- 192.168.77.10 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.401/0.538/0.675/0.137 ms
Fazit
Das automatische Verlinken von Docker-Containern ist im Docker-Daemon aktuell nur auf demselben Host möglich. Das Verbinden von Containern über Hostgrenzen hinweg ist zur Zeit noch etwas manueller Aufwand. Wir dürfen gespannt sein, wann das Docker-Team bzw. die Community auch hier eine Lösung anbieten werden.
Wer das obige Setup automatisiert aufsetzen möchte, findet in meinem Network Playground mit dem Simple-Setup eine vorbereitete Lösung zum Ausprobieren.
Im Prinzip ist man mit Pipework in der Lage, komplexere Netzwerkarchitekturen aufzubauen. Einen weiteren Schritt in Richtung Netzwerkvirtualisierung und Software-Defined Network stellt Open vSwitch dar. Das werden wir im nächsten Post weiter beleuchten.
– Andreas