FRnOG33 - How to put a bird on a docker container on arista?
Written by Arnaud no commentsThis is the presentation I made at FRnOG #33 to explain how we virtualized our route-server services at HOPUS.net with BIRD, docker on arista EOS : https://afenioux.fr/doc/presentations/FRnOG33-2019.pdf
Keywords are : #Docker #Arista #BIRD #route-server #NFV #SDN #bingo!
(Scripts and details below)
Run the docker image
•Pull the image and run itright here, right now!
•https://hub.docker.com/r/afenioux/bird
•Docker is supported on Arista EOS from 4.20.1F and onwards
bash sudo su
service docker start
Starting docker (via systemctl): [ OK ]
docker run -d -p 179:179 -v /mnt/flash/docker/bird:/etc/bird:rw --name rs --memory 512m --memory-swap 512m --cpus 1.1 afenioux/bird
Unable to find image 'afenioux/bird:latest' locally
latest: Pulling from afenioux/bird
176ce20f5fa13658bc8b8068d599ee3044a2e3b0f0959603bdc2cf4b5baa8349
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
176ce20f5fa1 afenioux/bird "/bin/sh -c 'bird -d'" 20 seconds ago Up 19 second 179 rs
docker logs rs
2019-07-12 15:27:37 <INFO> Started
docker exec -it rs birdc show protocols
BIRD 2.0.4 ready.
Name Proto Table State Since Info
device1 Device --- up 2019-07-12 15:27:43
iBGP BGP --- up 2019-07-12 15:27:56 Established
iBGP_v6 BGP --- up 2019-07-12 15:28:43 Established
Build and deploy an image yourself
Multi-stage image based on debian:stable : 132MB https://hub.docker.com/r/afenioux/bird/dockerfile
docker build -t registry.hopus.net/bird:2.0.4 --build-arg BIRDV=2.0.4 .
Sending build context to Docker daemon 3.584kB
Step 1/19 : FROM debian:stable AS compiler
---> 22f4c938cdc8
...
Successfully built 3bf87da26f2c
Successfully tagged registry.hopus.net/bird:2.0.4
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.hopus.net/bird 2.0.4 3bf87da26f2c 28 seconds ago 132MB
docker history registry.hopus.net/bird:2.0.4
IMAGE CREATED CREATED BY SIZE
3bf87da26f2c About a minute ago /bin/sh -c #(nop) CMD ["/bin/sh" "-c" "bird… 0B
8183c946e908 About a minute ago |1 BIRDV=2.0.4 /bin/sh -c apt-get install -y… 6.96MB
df609b80e3a7 About a minute ago |1 BIRDV=2.0.4 /bin/sh -c echo $TZ > /etc/ti… 1.46MB
05ecdc4530b2 About a minute ago /bin/sh -c #(nop) ENV TZ=Europe/Paris 0B
b1cbdcc1aa18 About a minute ago |1 BIRDV=2.0.4 /bin/sh -c mkdir /etc/bird /r… 0B
539cf16c5236 About a minute ago /bin/sh -c #(nop) COPY multi:dc6b1122ade7326… 4.48MB
dd51f8b7b6fd 2 months ago /bin/sh -c #(nop) ARG BIRDV=2.0.4 0B
c3a3916906fa 2 months ago /bin/sh -c apt-get update && apt-get install… 18.1MB
22f4c938cdc8 3 months ago /bin/sh -c #(nop) CMD ["bash"] 0B
<missing> 3 months ago /bin/sh -c #(nop) ADD file:d891105338b1a0ab1… 101MB
docker push registry.hopus.net/bird:2.0.4
2.0.4: digest: sha256:001b1b05a66e815cc90f369fa43647bff5b9a450f09da1ba10c4020413e4bf72 size: 1580
Create a registry (and enable IPv6…)
On the registry VM :
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
echo "deb https://download.docker.com/linux/debian stretch stable" > /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install docker-ce docker-ce-cli containerd.io
docker run --entrypoint htpasswd registry:2 -Bbn the_user the_password >> /etc/docker/auth/htpasswd
docker run -d -p 80 --restart=always --name registry -v /etc/docker/auth:/auth \
-e REGISTRY_HTTP_ADDR=0.0.0.0:80 -e “REGISTRY_AUTH=htpasswd" \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2
On the client (Docker host) :
/etc/docker/daemon.json
{
"insecure-registries":["registry.hopus.net"],
"ipv6": true,
"fixed-cidr-v6": "2001:db8:1::/64"
}
docker login registry.hopus.net
Port 179 is already used by Rib on EOS. Instead of publishing the port (docker run -p 179) we will use iptable to do Destination NAT (as Docker does anyway) and set the IP of the container manually.
The Rib BGP daemon from EOS is local, so it can’t reach $IP_RS, but you can locally peer with the IP 172.16.0.2 even if port 179 isn’t published (same for IPv6).
interface loopback 1
description IP_RS for Docker
ip address ${IP_RS}/32
ipv6 address ${IP6_RS}/128
docker network create --subnet=172.16.0.0/24 --ipv6 --subnet=fd00:172:16::/64 hopusnet
docker run -d -v /mnt/flash/docker/bird:/etc/bird --name rs --hostname rs --memory 512m \
--cpus 1.1 --net hopusnet --ip 172.16.0.2 --ip6 fd00:172:16::2 registry.hopus.net/bird:$BIRD_VER
iptables -t nat -I PREROUTING --dst ${IP_RS}/32 -p tcp --dport 179 -j DNAT --to 172.16.0.2
ip6tables -t nat -I PREROUTING --dst ${IP6_RS}/128 -p tcp --dport 179 -j DNAT --to fd00:172:16::2
Wanna SDN?
Scheduling an event-handler is king of SDN, right?
conf session rs
schedule refresh-rs-config at 11:00:00 interval 1440 timeout 60 max-log-files 7 command bash /mnt/flash/docker/refresh-rs-config.sh
event-handler docker-start
trigger on-boot
action bash /mnt/flash/docker/docker-start.sh
delay 480
asynchronous
timeout 120
exit
commit
docker-start.sh
# Written by Arnaud Fenioux
# Start docker and the route-server container at startup
# version 1.1
# Edit the lines below :
BIRD_VER="2.0.4"
IP_RS="X.X.X.Y"
IP6_RS="xxxx:xxxx:X::Y"
# Do not edit after this line, unless...
BIN=$(echo $0 | awk -F '/' '{print $NF}')
function print_log {
echo "$BIN: $1"
FastCli -p5 -c "send log message $BIN: $1"
}
print_log "Copying /mnt/flash/docker/daemon.json to /etc/docker..."
if [ ! -d "/etc/docker" ]; then
mkdir /etc/docker
fi
cp /mnt/flash/docker/daemon.json /etc/docker
print_log "Starting docker..."
service docker start
print_log "Signing in to registry..."
docker login -u <user> -p <password> registry.fqdn
print_log "Creating network... "
docker network create --subnet=172.16.0.0/24 --ipv6 --subnet=fd00:172:16::/64 mynet
print_log "Starting RS container..."
docker run -d -v /mnt/flash/docker/bird-rs:/etc/bird --name rs --hostname rs --memory 512m --cpus 1.1 --net mynet --ip 172.16.0.2 --ip6 fd00:172:16::2 registry.fqdn/bird:$BIRD_VER
print_log "Adding ip(6)tables rules to RS container... "
iptables -t nat -I PREROUTING --dst ${IP_RS}/32 -p tcp --dport 179 -j DNAT --to 172.16.0.2
ip6tables -t nat -I PREROUTING --dst ${IP6_RS}/128 -p tcp --dport 179 -j DNAT --to fd00:172:16::2
print_log "All done!"
exit 0
refresh-rs-config.sh
# Written by Arnaud Fenioux
# Get configuration file for route-servers and reload bird
# version 1.1
BIRD_CONF=bird_members.conf
WORKDIR=/mnt/flash/docker/bird-rs
BIN=$(echo $0 | awk -F '/' '{print $NF}')
function print_log {
echo "$BIN: $1"
FastCli -p5 -c "send log message $BIN: $1"
}
function exit_error {
mv $BIRD_CONF ${BIRD_CONF}.fail
[[ -e ${BIRD_CONF}.last ]] && mv ${BIRD_CONF}.last $BIRD_CONF
print_log "Exiting on error : $1"
exit 1
}
function exit_ok {
[[ -e ${BIRD_CONF}.last ]] && rm ${BIRD_CONF}.last
print_log "Terminated with success!"
exit 0
}
print_log "Started"
cd $WORKDIR
[[ -e $BIRD_CONF ]] && cp $BIRD_CONF ${BIRD_CONF}.last
#fetch_file http://hostname/get-rs-config.py?host=$HOSTNAME -o $BIRD_CONF
#The default timeout (-T) is 900 second.
#The default is to retry 20 times (-t).
wget -T 4 -t 3 -q -O $BIRD_CONF http://hostname/get-rs-config.py?host=$HOSTNAME
[[ $? -ne 0 ]] && exit_error "Can not fetch file"
# Checking that the file has (has not) expected lines
grep -q "Error:" $BIRD_CONF && exit_error "Bad config file (Error)"
grep -q "That's all folks!" $BIRD_CONF || exit_error "Bad config file (No EOF)"
# Same files no need to reload
diff -q -I "# Time is" $BIRD_CONF ${BIRD_CONF}.last && exit_ok
# New config, reload needed
print_log "Checking and reloading bird config"
docker exec rs birdc "configure check" | grep -q "Configuration OK"
[[ $? -ne 0 ]] && exit_error "Configuration check failed"
docker exec rs birdc "configure soft"
[[ $? -ne 0 ]] && exit_error "Configuration reload failed"
exit_ok