Parameterize HOST_IP everywhere -- portable to another lab host

Removes hardcoded 10.40.40.202 references so a fresh clone + .env-only
edit can stand the stack up on a new compute node.

  * docker-compose.yml: rib-poller PG_DSN now uses ${HOST_IP:-...}.
  * obmp-rib-poller/poller.py: default PG_DSN host falls back to
    ${HOST_IP} env (compose passes it; manual runs honour $HOST_IP too).
  * cml/gobgp_peering_config.py: GOBGP_IP read from $HOST_IP or the
    HOST_IP= line in repo-root .env, with a small _env_default helper.
  * cml/proxmox_bmp_config.py: COLLECTOR_HOST resolved the same way.

For gobgp/gobgpd.conf and gobgp-evpn/gobgpd.conf -- jauderho/gobgp is
distroless (no shell), so we can't sed-substitute at container start.
Pattern instead:

  * gobgpd.conf is now gobgpd.conf.tmpl with __HOST_IP__ placeholders
    (committed). The rendered gobgpd.conf is gitignored.
  * setup.sh renders the .tmpl(s) to .conf using $HOST_IP from .env.
  * compose `command` stays the simple `gobgpd -f /config/gobgpd.conf`.

After cloning on a new host:  cp .env.example .env  -> edit HOST_IP ->
./setup.sh -> docker compose up -d. Verified locally by force-recreating
gobgp; all 6 sessions (4 cores + 2 Bromirski) re-established in <60s.

Known portability gaps still to address (separate work):
  * Hardcoded lab-router inventories in cml/*.py and
    obmp-rib-poller/poller.py.
  * The /etc/cron.d/openbmp */5 -> */15 edit inside obmp-psql-app is
    not persistent (regenerated by config_cron on every container start).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sam 2026-05-19 18:34:51 -07:00
parent 2a82bd9a94
commit 2634aada24
8 changed files with 68 additions and 12 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@
__pycache__/ __pycache__/
*.pyc *.pyc
gobgp/gobgpd.conf
gobgp-evpn/gobgpd.conf

View File

@ -33,14 +33,32 @@ policy from the cores. To stop the feed instantly without touching router
config, `docker compose stop gobgp` -- the eBGP sessions drop and the full config, `docker compose stop gobgp` -- the eBGP sessions drop and the full
table is withdrawn fleet-wide within seconds. See gobgp/README.md. table is withdrawn fleet-wide within seconds. See gobgp/README.md.
""" """
import os
import sys import sys
import time import time
import paramiko import paramiko
def _env_default(key, default, dotenv=".env"):
"""Resolve a value from os.environ or the repo-root .env, else default."""
v = os.environ.get(key)
if v:
return v
try:
with open(dotenv) as fh:
for line in fh:
s = line.strip()
if s and not s.startswith("#") and s.startswith(f"{key}="):
return s.split("=", 1)[1].strip().strip('"').strip("'")
except FileNotFoundError:
pass
return default
# GoBGP runs network_mode: host, so it sources BGP TCP from the host's real # GoBGP runs network_mode: host, so it sources BGP TCP from the host's real
# interface IP (10.40.40.202) -- NOT its router-id 10.40.40.250. The cores # interface IP -- NOT its router-id. The cores must peer with the host IP.
# must peer with the host IP. # Resolved from $HOST_IP or the HOST_IP= line in repo-root .env.
GOBGP_IP = "10.40.40.202" GOBGP_IP = _env_default("HOST_IP", "10.40.40.202")
GOBGP_AS = "65001" GOBGP_AS = "65001"
# Additive config, built per core (asn = that core's local BGP AS: # Additive config, built per core (asn = that core's local BGP AS:

View File

@ -18,12 +18,31 @@ Verify afterwards in OpenBMP:
-c "SELECT name, ip_address, bgp_id, isconnected FROM routers ORDER BY name;" -c "SELECT name, ip_address, bgp_id, isconnected FROM routers ORDER BY name;"
""" """
import os
import sys import sys
import time import time
import paramiko import paramiko
def _env_default(key, default, dotenv=".env"):
"""Resolve a value from os.environ or the repo-root .env, else default."""
v = os.environ.get(key)
if v:
return v
try:
with open(dotenv) as fh:
for line in fh:
s = line.strip()
if s and not s.startswith("#") and s.startswith(f"{key}="):
return s.split("=", 1)[1].strip().strip('"').strip("'")
except FileNotFoundError:
pass
return default
# --- BMP collector --------------------------------------------------------- # --- BMP collector ---------------------------------------------------------
COLLECTOR_HOST = "10.40.40.202" # Resolved from $HOST_IP or the HOST_IP= line in repo-root .env.
COLLECTOR_HOST = _env_default("HOST_IP", "10.40.40.202")
COLLECTOR_PORT = "5000" COLLECTOR_PORT = "5000"
# `bmp server 1` block — flat formal form, identical to the ESXi lab. # `bmp server 1` block — flat formal form, identical to the ESXi lab.

View File

@ -476,12 +476,16 @@ services:
# Host networking: the daemon uses the host's real IPv4 + IPv6 stack, so # Host networking: the daemon uses the host's real IPv4 + IPv6 stack, so
# both the v4 and v6 eBGP sessions to AS57355 source from the host's # both the v4 and v6 eBGP sessions to AS57355 source from the host's
# public addresses (no Docker IPv6/NAT plumbing). BMP still reaches the # public addresses (no Docker IPv6/NAT plumbing). BMP still reaches the
# collector on 10.40.40.202:5000 (its published port). # collector on $HOST_IP:5000 (its published port).
network_mode: host network_mode: host
depends_on: depends_on:
- collector - collector
# gobgpd reads /config/gobgpd.conf; the same mount carries mrt-refresh.sh # gobgpd.conf.tmpl (in git, with __HOST_IP__ placeholders) is rendered to
# and the cached MRT dumps it downloads. # gobgpd.conf (gitignored) by setup.sh using $HOST_IP from .env. The
# gobgp image is distroless (no shell), so we cannot render at container
# start -- the rendered .conf must exist on the host before `docker
# compose up`. The same /config mount also carries mrt-refresh.sh and
# cached MRT dumps.
volumes: volumes:
- ./gobgp:/config - ./gobgp:/config
command: ["gobgpd", "-f", "/config/gobgpd.conf", "-t", "toml"] command: ["gobgpd", "-f", "/config/gobgpd.conf", "-t", "toml"]
@ -498,6 +502,8 @@ services:
image: jauderho/gobgp:v4.5.0 image: jauderho/gobgp:v4.5.0
depends_on: depends_on:
- collector - collector
# gobgpd.conf is rendered by setup.sh from gobgpd.conf.tmpl. See gobgp/
# service notes above.
volumes: volumes:
- ./gobgp-evpn:/config - ./gobgp-evpn:/config
command: ["gobgpd", "-f", "/config/gobgpd.conf", "-t", "toml"] command: ["gobgpd", "-f", "/config/gobgpd.conf", "-t", "toml"]
@ -534,7 +540,7 @@ services:
depends_on: depends_on:
- psql - psql
environment: environment:
- PG_DSN=host=10.40.40.202 port=5432 dbname=openbmp user=openbmp password=${POSTGRES_PASSWORD:-openbmp} - PG_DSN=host=${HOST_IP:-10.40.40.202} port=5432 dbname=openbmp user=openbmp password=${POSTGRES_PASSWORD:-openbmp}
- POLL_INTERVAL=900 - POLL_INTERVAL=900
- ROUTER_USER=webui - ROUTER_USER=webui
- ROUTER_PASS=cisco - ROUTER_PASS=cisco

View File

@ -21,7 +21,7 @@
# --- BMP export to the OpenBMP collector ------------------------------------ # --- BMP export to the OpenBMP collector ------------------------------------
[[bmp-servers]] [[bmp-servers]]
[bmp-servers.config] [bmp-servers.config]
address = "10.40.40.202" address = "__HOST_IP__"
port = 5000 port = 5000
# local-rib: the injected EVPN routes live in the loc-rib (there are no # local-rib: the injected EVPN routes live in the loc-rib (there are no
# BGP peers / no adj-rib-in), so export the local RIB. # BGP peers / no adj-rib-in), so export the local RIB.

View File

@ -77,7 +77,7 @@
# --- Neighbor: CML CORE-01 (AS65020) ---------------------------------------- # --- Neighbor: CML CORE-01 (AS65020) ----------------------------------------
# GoBGP initiates outbound to the core's mgmt IP (reachable from the docker # GoBGP initiates outbound to the core's mgmt IP (reachable from the docker
# host -- the cores already reach the host for BMP). GoBGP sources the session # host -- the cores already reach the host for BMP). GoBGP sources the session
# from the host IP 10.40.40.202. eBGP multihop: the host is several hops from # from the host IP __HOST_IP__. eBGP multihop: the host is several hops from
# the core. Default export policy (accept) re-advertises the full Bromirski # the core. Default export policy (accept) re-advertises the full Bromirski
# table to the core. prefix-limit is a safety cap on what the core can send # table to the core. prefix-limit is a safety cap on what the core can send
# back (its lab routes only -- small). # back (its lab routes only -- small).
@ -164,7 +164,7 @@
# routes, pre import-policy) -- consistent with the rest of the OpenBMP fleet. # routes, pre import-policy) -- consistent with the rest of the OpenBMP fleet.
[[bmp-servers]] [[bmp-servers]]
[bmp-servers.config] [bmp-servers.config]
address = "10.40.40.202" address = "__HOST_IP__"
port = 5000 port = 5000
route-monitoring-policy = "pre-policy" route-monitoring-policy = "pre-policy"
statistics-timeout = 3600 statistics-timeout = 3600

View File

@ -36,7 +36,8 @@ from ncclient import manager
PG_DSN = os.environ.get( PG_DSN = os.environ.get(
"PG_DSN", "PG_DSN",
"host=10.40.40.202 port=5432 dbname=openbmp user=openbmp password=openbmp", f"host={os.environ.get('HOST_IP', 'localhost')} port=5432 "
"dbname=openbmp user=openbmp password=openbmp",
) )
POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", "900")) POLL_INTERVAL = int(os.environ.get("POLL_INTERVAL", "900"))
R_USER = os.environ.get("ROUTER_USER", "webui") R_USER = os.environ.get("ROUTER_USER", "webui")

View File

@ -111,6 +111,16 @@ else
echo "authelia/users_database.yml exists — left untouched" echo "authelia/users_database.yml exists — left untouched"
fi fi
# --- gobgpd.conf rendering (host-side -- gobgp image is distroless) ---------
# Render gobgp{,-evpn}/gobgpd.conf from gobgpd.conf.tmpl, substituting
# __HOST_IP__ with $HOST_IP. The rendered .conf is gitignored.
for d in gobgp gobgp-evpn; do
if [ -f "$d/gobgpd.conf.tmpl" ]; then
sed -e "s/__HOST_IP__/$HOST_IP/g" "$d/gobgpd.conf.tmpl" > "$d/gobgpd.conf"
echo "Rendered $d/gobgpd.conf (HOST_IP=$HOST_IP)"
fi
done
# --- images ----------------------------------------------------------------- # --- images -----------------------------------------------------------------
echo "Pulling and building images ..." echo "Pulling and building images ..."
docker compose pull --quiet docker compose pull --quiet