From 7220a46582ebbfac93bcdb3a7f138bcf2fb03d6d Mon Sep 17 00:00:00 2001 From: Michael Costello Date: Thu, 21 May 2026 16:01:46 +0000 Subject: [PATCH 1/8] feat(.devcontainer): add containerlab DooD devcontainer Add a new containerlab-dood devcontainer for running ContainerLab network labs against the host Docker daemon. Relocate the existing Galactic devcontainer to .devcontainer/galactic/ to make room for multiple devcontainer configurations. Part of #83 --- .../containerlab-dood/devcontainer.json | 23 +++++++++ .../containerlab-dood/post-create.sh | 50 +++++++++++++++++++ .devcontainer/{ => galactic}/README.md | 0 .devcontainer/galactic/devcontainer-lock.json | 39 +++++++++++++++ .../{ => galactic}/devcontainer.json | 4 +- .devcontainer/{ => galactic}/post-create.sh | 11 ++-- README.md | 4 +- 7 files changed, 125 insertions(+), 6 deletions(-) create mode 100644 .devcontainer/containerlab-dood/devcontainer.json create mode 100755 .devcontainer/containerlab-dood/post-create.sh rename .devcontainer/{ => galactic}/README.md (100%) create mode 100644 .devcontainer/galactic/devcontainer-lock.json rename .devcontainer/{ => galactic}/devcontainer.json (96%) rename .devcontainer/{ => galactic}/post-create.sh (91%) diff --git a/.devcontainer/containerlab-dood/devcontainer.json b/.devcontainer/containerlab-dood/devcontainer.json new file mode 100644 index 0000000..9145629 --- /dev/null +++ b/.devcontainer/containerlab-dood/devcontainer.json @@ -0,0 +1,23 @@ +{ + "image": "ghcr.io/srl-labs/containerlab/devcontainer-dood-slim:0.75.0", + "runArgs": [ + "--network=host", + "--pid=host", + "--privileged" + ], + "mounts": [ + "type=bind,src=/run/docker/netns,dst=/run/docker/netns", + "type=bind,src=/var/lib/docker,dst=/var/lib/docker", + "type=bind,src=/lib/modules,dst=/lib/modules" + ], + "workspaceFolder": "${localWorkspaceFolder}", + "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", + "customizations": { + "vscode": { + "extensions": [ + "anthropic.claude-code" + ] + } + }, + "postCreateCommand": "bash .devcontainer/containerlab-dood/post-create.sh" +} \ No newline at end of file diff --git a/.devcontainer/containerlab-dood/post-create.sh b/.devcontainer/containerlab-dood/post-create.sh new file mode 100755 index 0000000..5170ef0 --- /dev/null +++ b/.devcontainer/containerlab-dood/post-create.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Starting post-create setup for Galactic containerlab environment..." + +# Install packages +echo "Installing Debian packages..." +sudo apt update +sudo DEBIAN_FRONTEND=noninteractive apt upgrade -y +sudo apt install -y \ + golang-go + +# Install Claude +curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - +sudo apt install -y nodejs +sudo npm install -g @anthropic-ai/claude-code + +# Install kubectl +echo "Installing kubectl..." +KUBECTL_VERSION=$(curl -fsSL https://dl.k8s.io/release/stable.txt) +curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/arm64/kubectl" \ + | sudo tee /usr/local/bin/kubectl > /dev/null +sudo chmod +x /usr/local/bin/kubectl + +# Install kind for local Kubernetes +echo "Installing Kind..." +GO111MODULE=on go install sigs.k8s.io/kind@latest + +# Install crane for pulling container images (Docker binaries in this environment +# panic on TLS 1.3 to Docker Hub due to an msft-golang/OpenSSL bug on ARM64) +echo "Installing crane..." +CRANE_VERSION=$(curl -fsSL https://api.github.com/repos/google/go-containerregistry/releases/latest | grep '"tag_name"' | cut -d'"' -f4) +curl -fsSL "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_arm64.tar.gz" \ + | sudo tar xz -C /usr/local/bin crane + +# Disable atuin's up-arrow TUI (keep atuin for ctrl-r but restore normal up-arrow history) +sed -i 's/eval "$(atuin init zsh)"/eval "$(atuin init zsh --disable-up-arrow)"/' ~/.zshrc + +# Verify installations +echo "" +echo "Verifying installations..." +echo "Go version: $(go version)" +echo "Python version: $(python3 --version)" +echo "kubectl version: $(kubectl version --client --short 2>/dev/null || echo 'kubectl not installed')" +echo "kind version: $(kind version)" +echo "kustomize version: $(kustomize version --short 2>/dev/null || echo 'kustomize not installed')" +echo "Docker version: $(docker --version)" + +echo "" +echo "Post-create setup completed successfully!" diff --git a/.devcontainer/README.md b/.devcontainer/galactic/README.md similarity index 100% rename from .devcontainer/README.md rename to .devcontainer/galactic/README.md diff --git a/.devcontainer/galactic/devcontainer-lock.json b/.devcontainer/galactic/devcontainer-lock.json new file mode 100644 index 0000000..adc9623 --- /dev/null +++ b/.devcontainer/galactic/devcontainer-lock.json @@ -0,0 +1,39 @@ +{ + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "version": "2.5.8", + "resolved": "ghcr.io/devcontainers/features/common-utils@sha256:c42fdefe6d737a3a6f61cc52b23c7c9a565d08cc4d9c303669a7cf2ee5fd81fc", + "integrity": "sha256:c42fdefe6d737a3a6f61cc52b23c7c9a565d08cc4d9c303669a7cf2ee5fd81fc" + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "version": "2.17.0", + "resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c", + "integrity": "sha256:25b9f05705ffba7dbe503230ac76081419306f8c8bc88e0ce78c4ecd99a0c78c" + }, + "ghcr.io/devcontainers/features/git:1": { + "version": "1.3.5", + "resolved": "ghcr.io/devcontainers/features/git@sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251", + "integrity": "sha256:27905dc196c01f77d6ba8709cb82eeaf330b3b108772e2f02d1cd0d826de1251" + }, + "ghcr.io/devcontainers/features/go:1": { + "version": "1.3.4", + "resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032", + "integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032" + }, + "ghcr.io/devcontainers/features/kubectl-helm-minikube:1": { + "version": "1.3.1", + "resolved": "ghcr.io/devcontainers/features/kubectl-helm-minikube@sha256:bbe8adf6b37fff8c67412ab0a4579f4c2f30bbaba1d9a5cebd9e38bade54025b", + "integrity": "sha256:bbe8adf6b37fff8c67412ab0a4579f4c2f30bbaba1d9a5cebd9e38bade54025b" + }, + "ghcr.io/devcontainers/features/node:2": { + "version": "2.0.0", + "resolved": "ghcr.io/devcontainers/features/node@sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f", + "integrity": "sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f" + }, + "ghcr.io/devcontainers/features/python:1": { + "version": "1.8.0", + "resolved": "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511", + "integrity": "sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/galactic/devcontainer.json similarity index 96% rename from .devcontainer/devcontainer.json rename to .devcontainer/galactic/devcontainer.json index 336365b..df49f0a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/galactic/devcontainer.json @@ -1,6 +1,6 @@ { "name": "galactic", - "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04", "features": { "ghcr.io/devcontainers/features/go:1": { "version": "1.24.2" @@ -131,7 +131,7 @@ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind", "source=${localEnv:HOME}${localEnv:USERPROFILE}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached" ], - "postCreateCommand": "bash .devcontainer/post-create.sh", + "postCreateCommand": "bash .devcontainer/galactic/post-create.sh", "runArgs": [ "--privileged", "--cap-add=NET_ADMIN", diff --git a/.devcontainer/post-create.sh b/.devcontainer/galactic/post-create.sh similarity index 91% rename from .devcontainer/post-create.sh rename to .devcontainer/galactic/post-create.sh index d541058..ee7da73 100755 --- a/.devcontainer/post-create.sh +++ b/.devcontainer/galactic/post-create.sh @@ -33,8 +33,13 @@ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest # Install network tools echo "Installing network tools..." -sudo apt-get update -sudo apt-get install -y \ +sudo apt update +sudo apt install -y software-properties-common +sudo add-apt-repository -y ppa:apt-fast/stable +sudo apt update +sudo DEBIAN_FRONTEND=noninteractive apt install -y apt-fast +sudo apt-fast upgrade -y +sudo apt-fast install -y \ iproute2 \ iptables \ tcpdump \ @@ -62,7 +67,7 @@ make manifests generate # Set up git safe directory echo "Configuring git safe directory..." -git config --global --add safe.directory /workspaces/galactic +git config --add safe.directory /workspaces/galactic # Install Claude Code CLI echo "Installing Claude Code..." diff --git a/README.md b/README.md index 4ebbe7d..8970943 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,9 @@ Under the hood, Galactic uses Segment Routing over IPv6 (SRv6) for efficient, de ## Getting Started -See the [`lab/`](./lab/) directory for example topologies and the [DevContainer](./.devcontainer/) for development environment setup. +- See the [`lab/`](./lab/) directory for example topologies. +- See the [DevContainer](./.devcontainer/galactic/) for development environment setup. +- (macOS) See the [Containerlab DevContainer](./.devcontainer/containerlab-dood/) for running containerlab on ARM64. ## License From b75382c4d5d2d95a11807b11850ad86220a5e17b Mon Sep 17 00:00:00 2001 From: Michael Costello Date: Thu, 21 May 2026 16:01:46 +0000 Subject: [PATCH 2/8] feat(lab/network): add crane-based image pull and simplify host setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a pull-base target that uses crane to fetch debian:bookworm-slim, working around the TLS 1.3 panic in OrbStack's msft-golang Docker builds. Remove the --dualstack flag from host-setup.sh — the lab unconditionally enables both IPv4 and IPv6 forwarding. Part of #83 --- lab/network/Makefile | 15 ++++++++++++--- lab/network/scripts/host-setup.sh | 21 ++++----------------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/lab/network/Makefile b/lab/network/Makefile index d0121c4..fd162ef 100644 --- a/lab/network/Makefile +++ b/lab/network/Makefile @@ -1,7 +1,8 @@ LAB := $(shell awk '/^name:/ {print $$2; exit}' *.clab.yaml) TOPO := $(wildcard *.clab.yaml) +ARCH := $(shell uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') -.PHONY: help build up down reload inspect graph host-setup clean +.PHONY: help pull-base build up down reload inspect graph host-setup clean .DEFAULT_GOAL := help @@ -10,8 +11,16 @@ help: ## Show available targets | awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-24s\033[0m %s\n", $$1, $$2}' \ | sort -build: ## Build the custom GoBGP + FRR image - docker build -t gobgp-pe:latest containers/gobgp-pe +pull-base: ## Pull the base image for gobgp-pe (uses crane; Docker Hub TLS 1.3 panics in OrbStack's msft-golang Docker builds) + @if ! docker image inspect debian:bookworm-slim >/dev/null 2>&1; then \ + crane pull --platform linux/$(ARCH) \ + debian:bookworm-slim /tmp/debian-bookworm-slim.tar; \ + docker load -i /tmp/debian-bookworm-slim.tar; \ + rm /tmp/debian-bookworm-slim.tar; \ + fi + +build: pull-base ## Build the custom GoBGP + FRR image + docker build --pull=false -t gobgp-pe:latest containers/gobgp-pe up: build host-setup ## Build the image and deploy the lab sudo containerlab deploy -t $(TOPO) diff --git a/lab/network/scripts/host-setup.sh b/lab/network/scripts/host-setup.sh index aa0f637..963f676 100755 --- a/lab/network/scripts/host-setup.sh +++ b/lab/network/scripts/host-setup.sh @@ -3,21 +3,18 @@ set -euo pipefail -DUALSTACK=false PERSIST=true SYSCTL_CONF=/etc/sysctl.d/99-clab.conf usage() { - echo "Usage: $0 [--dualstack] [--no-persist]" + echo "Usage: $0 [--no-persist]" echo "" - echo " --dualstack Enable IPv4 forwarding in addition to IPv6" echo " --no-persist Apply sysctls now but do not write to ${SYSCTL_CONF}" exit 1 } for arg in "$@"; do case $arg in - --dualstack) DUALSTACK=true ;; --no-persist) PERSIST=false ;; --help|-h) usage ;; *) echo "Unknown argument: $arg"; usage ;; @@ -30,7 +27,6 @@ if [ "$(id -u)" -ne 0 ]; then fi echo "==> Containerlab host setup" -echo " dualstack : ${DUALSTACK}" echo " persist : ${PERSIST}" echo "" @@ -38,13 +34,8 @@ echo "--> Enabling IPv6 forwarding" sysctl -w net.ipv6.conf.all.forwarding=1 sysctl -w net.ipv6.conf.default.forwarding=1 -if [ "$DUALSTACK" = true ]; then - echo "--> Enabling IPv4 forwarding (dual-stack)" - sysctl -w net.ipv4.ip_forward=1 -else - echo "--> Disabling IPv4 forwarding (IPv6-only lab)" - sysctl -w net.ipv4.ip_forward=0 -fi +echo "--> Enabling IPv4 forwarding" +sysctl -w net.ipv4.ip_forward=1 if [ "$PERSIST" = true ]; then echo "--> Writing ${SYSCTL_CONF}" @@ -53,11 +44,7 @@ if [ "$PERSIST" = true ]; then echo "# Generated by host-setup.sh" echo "net.ipv6.conf.all.forwarding=1" echo "net.ipv6.conf.default.forwarding=1" - if [ "$DUALSTACK" = true ]; then - echo "net.ipv4.ip_forward=1" - else - echo "net.ipv4.ip_forward=0" - fi + echo "net.ipv4.ip_forward=1" } >"${SYSCTL_CONF}" echo " written." fi From d866167ec8f5fdb50f57531356c06a6572f05fd6 Mon Sep 17 00:00:00 2001 From: Michael Costello Date: Thu, 21 May 2026 16:01:46 +0000 Subject: [PATCH 3/8] feat(lab/containers): add Kind cluster tooling for OrbStack DooD Add Makefile with targets for building the Galactic Kind node image, creating/inspecting/tearing down the cluster, and running node setup. Use crane for base image pulls to work around the TLS 1.3 panic in OrbStack's msft-golang Docker builds. Wrap kubectl in the node image to poll the apiserver before applying manifests, fixing a race where kind's StorageClass step hits the brief window after kubeadm init where OrbStack's bridge interface is still coming up. Enable Cilium's full kube-proxy replacement to avoid conflicts between iptables-based service routing and the SRv6/VRF datapath. Part of #83 --- lab/containers/Makefile | 48 +++++++++++++++++++ .../kindest-node-galactic/Dockerfile | 6 +++ .../kindest-node-galactic/kubectl-wrapper | 9 ++++ .../kindest-node-galactic/scripts/install.sh | 2 +- 4 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 lab/containers/Makefile create mode 100644 lab/containers/kindest-node-galactic/kubectl-wrapper diff --git a/lab/containers/Makefile b/lab/containers/Makefile new file mode 100644 index 0000000..32fc829 --- /dev/null +++ b/lab/containers/Makefile @@ -0,0 +1,48 @@ +.PHONY: help build pull-base create host-setup status + +.DEFAULT_GOAL := help + +KINDEST_VER := v1.35.1 +ARCH := $(shell uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') + +help: ## Show available targets + @grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) \ + | awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-24s\033[0m %s\n", $$1, $$2}' \ + | sort + +pull-base: ## Pull the base Kind node image (uses crane; Docker Hub TLS 1.3 panics in OrbStack's msft-golang Docker builds) + @if ! docker image inspect kindest/node:$(KINDEST_VER) >/dev/null 2>&1; then \ + crane pull --platform linux/$(ARCH) \ + kindest/node:$(KINDEST_VER) /tmp/kindest-node-$(KINDEST_VER).tar; \ + docker load -i /tmp/kindest-node-$(KINDEST_VER).tar; \ + rm /tmp/kindest-node-$(KINDEST_VER).tar; \ + fi + +build: pull-base ## Build the Galactic image + docker build --pull=false -t kindest/node:galactic ./kindest-node-galactic/ + +create: ## Create the kind cluster + @if kind get clusters 2>/dev/null | grep -q '^kind$$'; then \ + echo "kind cluster already exists, skipping create"; \ + else \ + kind create cluster --config kind-config.yaml; \ + fi + kind export kubeconfig + +host-setup: ## Setup each node in the cluster + for node in $$(kind get nodes); do \ + docker exec "$$node" /galactic/scripts/install.sh; \ + done + +status: ## Show cluster nodes, pods, deployments, and services + @echo "\n=== Nodes ==="; \ + kubectl get nodes -o wide; \ + echo "\n=== Pods ==="; \ + kubectl get pods -A -o wide | egrep '^galactic|^NAMESPACE'; \ + echo "\n=== Deployments ==="; \ + kubectl get deployments -A | egrep '^galactic|^NAMESPACE'; \ + echo "\n=== Services ==="; \ + kubectl get services -A | egrep '^galactic|^NAMESPACE'; + +clean: ## Delete kind cluster + kind delete cluster diff --git a/lab/containers/kindest-node-galactic/Dockerfile b/lab/containers/kindest-node-galactic/Dockerfile index 7a07c5f..1b959ac 100644 --- a/lab/containers/kindest-node-galactic/Dockerfile +++ b/lab/containers/kindest-node-galactic/Dockerfile @@ -11,3 +11,9 @@ WORKDIR /galactic COPY --chmod=0755 scripts/ ./scripts/ COPY resources/ ./resources/ + +# Wrap kubectl to work around OrbStack DooD: kind runs `kubectl apply` for the +# StorageClass step immediately after kubeadm init, but the apiserver briefly +# goes offline while OrbStack's bridge finishes coming up, causing worker nodes +# to never reach kubeadm join. The wrapper polls /healthz before passing through. +COPY --chmod=0755 kubectl-wrapper /usr/local/bin/kubectl diff --git a/lab/containers/kindest-node-galactic/kubectl-wrapper b/lab/containers/kindest-node-galactic/kubectl-wrapper new file mode 100644 index 0000000..929a025 --- /dev/null +++ b/lab/containers/kindest-node-galactic/kubectl-wrapper @@ -0,0 +1,9 @@ +#!/bin/bash +# In OrbStack DooD, the kubelet briefly restarts the apiserver static pod after +# kubeadm init exits, creating a window where nothing listens on 6443. Kind +# runs the StorageClass step immediately after kubeadm and hits this window. +# Poll /healthz until the apiserver is actually accepting connections first. +until curl -sk https://127.0.0.1:6443/healthz >/dev/null 2>&1; do + sleep 1 +done +exec /usr/bin/kubectl "$@" --server=https://127.0.0.1:6443 diff --git a/lab/containers/kindest-node-galactic/scripts/install.sh b/lab/containers/kindest-node-galactic/scripts/install.sh index d7e7123..c301b14 100644 --- a/lab/containers/kindest-node-galactic/scripts/install.sh +++ b/lab/containers/kindest-node-galactic/scripts/install.sh @@ -19,7 +19,7 @@ if hostname |grep -q control-plane; then # control-plane # Cilium curl -L https://github.com/cilium/cilium-cli/releases/download/${CILIUM_VERSION}/cilium-linux-${ARCH}.tar.gz |tar xvfz - -C /usr/local/bin && chmod +x /usr/local/bin/cilium - cilium install --set cni.exclusive=false && cilium status --wait + cilium install --set cni.exclusive=false --set kubeProxyReplacement=true && cilium status --wait # Cert Manager kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/${CERTMANAGER_VERSION}/cert-manager.yaml From 9653ea69ef0d846f5d077f01487fa613cc3404e6 Mon Sep 17 00:00:00 2001 From: Michael Costello Date: Thu, 21 May 2026 16:05:01 +0000 Subject: [PATCH 4/8] fix(.devcontainer): mount host gitconfig into containerlab-dood Part of #83 --- .devcontainer/containerlab-dood/devcontainer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/containerlab-dood/devcontainer.json b/.devcontainer/containerlab-dood/devcontainer.json index 9145629..a205a04 100644 --- a/.devcontainer/containerlab-dood/devcontainer.json +++ b/.devcontainer/containerlab-dood/devcontainer.json @@ -8,7 +8,8 @@ "mounts": [ "type=bind,src=/run/docker/netns,dst=/run/docker/netns", "type=bind,src=/var/lib/docker,dst=/var/lib/docker", - "type=bind,src=/lib/modules,dst=/lib/modules" + "type=bind,src=/lib/modules,dst=/lib/modules", + "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,consistency=cached" ], "workspaceFolder": "${localWorkspaceFolder}", "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", From 4d1c2d8a392bc418ade0a56c5bf223120f50f954 Mon Sep 17 00:00:00 2001 From: Michael Costello Date: Thu, 21 May 2026 20:11:48 +0000 Subject: [PATCH 5/8] fix(lab/gvpc): arm64 support and containerlab-dood devcontainer cleanup Fix the gvpc lab to run on arm64 in the containerlab-dood devcontainer: - Remove stale COPY resources/ from Dockerfile (resources moved to lab/gvpc/resources/ in the main merge and are applied at runtime) - Set wait: 0s on all kind clusters to bypass a kind v0.31.0 panic when the node conditions array is empty at fast startup; install.sh already polls kubectl get nodes for readiness - Use ARG KINDEST_VER in Dockerfile and pass it from the Makefile so the version has a single source of truth - Fold containers/Makefile into lab/gvpc/Makefile; remove dead targets (create, host-setup, status, clean) left over from the old lab/containers/ single-cluster setup - Upgrade Go to 1.24.5 in the containerlab-dood devcontainer via the devcontainer feature (matches ContainerLab's build and satisfies Galactic's go 1.24.0 requirement); remove apt golang-go - Update lab/README.md to reflect current structure (network/ + gvpc/, no containers/) - Fix lab/gvpc/README.md: add missing control/ and pe/ group_files entries; correct overlay target description Co-Authored-By: Claude Sonnet 4.6 --- .../containerlab-dood/devcontainer.json | 5 ++ .../containerlab-dood/post-create.sh | 3 - lab/README.md | 85 +++++-------------- lab/gvpc/Makefile | 12 ++- lab/gvpc/README.md | 4 +- lab/gvpc/containers/Makefile | 48 ----------- .../kindest-node-galactic/Dockerfile | 5 +- lab/gvpc/gvpc.clab.yaml | 6 +- 8 files changed, 44 insertions(+), 124 deletions(-) delete mode 100644 lab/gvpc/containers/Makefile diff --git a/.devcontainer/containerlab-dood/devcontainer.json b/.devcontainer/containerlab-dood/devcontainer.json index a205a04..790f66c 100644 --- a/.devcontainer/containerlab-dood/devcontainer.json +++ b/.devcontainer/containerlab-dood/devcontainer.json @@ -13,6 +13,11 @@ ], "workspaceFolder": "${localWorkspaceFolder}", "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", + "features": { + "ghcr.io/devcontainers/features/go:1": { + "version": "1.24.5" + } + }, "customizations": { "vscode": { "extensions": [ diff --git a/.devcontainer/containerlab-dood/post-create.sh b/.devcontainer/containerlab-dood/post-create.sh index 5170ef0..b326b1f 100755 --- a/.devcontainer/containerlab-dood/post-create.sh +++ b/.devcontainer/containerlab-dood/post-create.sh @@ -7,9 +7,6 @@ echo "Starting post-create setup for Galactic containerlab environment..." echo "Installing Debian packages..." sudo apt update sudo DEBIAN_FRONTEND=noninteractive apt upgrade -y -sudo apt install -y \ - golang-go - # Install Claude curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - sudo apt install -y nodejs diff --git a/lab/README.md b/lab/README.md index 0e2f879..db2cac2 100644 --- a/lab/README.md +++ b/lab/README.md @@ -4,9 +4,8 @@ Local development and integration-testing environments for [Galactic VPC](https: ``` lab/ -├── network/ # ContainerLab SRv6 underlay network -└── containers/ - └── kindest-node-galactic/ # Custom Kind node image with Galactic pre-installed +├── network/ # ContainerLab SRv6 underlay — standalone BGP/SRv6 network with FRR + GoBGP +└── gvpc/ # ContainerLab multi-cluster lab — three Kind clusters over an SRv6 transit mesh ``` --- @@ -38,76 +37,30 @@ make down # tear down --- -## `containers/kindest-node-galactic/` — Custom Kind Node Image +## `gvpc/` — Multi-Cluster GVPC Lab -A `kindest/node` image extended with the tooling and Kubernetes manifests needed to -run a full Galactic stack inside a [Kind](https://kind.sigs.k8s.io/) cluster. +A ContainerLab topology that connects three Kind clusters (`iad`, `sjc`, `infra`) over +an IPv6 SRv6 transit mesh. FRR runs as a node routing daemon on each cluster worker for +the eBGP underlay; GoBGP runs on `iad` and `sjc` workers to exchange L3VPN type-5 routes +with the `infra` route reflector over iBGP. Cilium, cert-manager, and Multus are +pre-installed on each cluster. -``` -kindest-node-galactic/ -├── Dockerfile -├── resources/ # Kubernetes manifests applied at cluster boot -│ ├── agent.k8s.yaml -│ ├── mqtt.k8s.yaml -│ ├── operator.k8s.yaml -│ └── router.k8s.yaml -└── scripts/ - └── install.sh # Installs Cilium, cert-manager, Multus, and Galactic -``` - -`install.sh` is invoked once per node after the cluster comes up. On the control-plane -node it applies each Kubernetes manifest in order (Cilium → cert-manager → Multus → -MQTT → Galactic operator → router → agent). On worker nodes it loads kernel modules, -sets SRv6 sysctls, and drops in the CNI binaries. +See [`gvpc/README.md`](gvpc/README.md) for topology details, addressing, and +verification commands. ### Prerequisites +- ContainerLab ≥ 0.54 - Docker -- [`kind`](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) -- Linux kernel with `vrf` module and SRv6 support (or a VM with those features) +- `kind` CLI +- Host kernel with SRv6 support -### Build +### Quick start (gvpc) ```bash -cd containers/kindest-node-galactic -docker build -t kindest/node:galactic . -``` - -### Use with Kind - -Reference the custom image in your Kind cluster config: - -```yaml -# kind-config.yaml -kind: Cluster -apiVersion: kind.x-k8s.io/v1alpha4 -nodes: - - role: control-plane - image: kindest/node:galactic - - role: worker - image: kindest/node:galactic - - role: worker - image: kindest/node:galactic -``` - -```bash -kind create cluster --config kind-config.yaml -``` - -After the cluster is up, run `install.sh` on each node: - -```bash -for node in $(kind get nodes); do - docker exec "$node" /galactic/scripts/install.sh -done +cd gvpc +make up # build Kind node image, apply host sysctls, deploy lab +make underlay # apply FRR DaemonSets to all three clusters +make overlay # pull GoBGP image, load into clusters, apply DaemonSets +make down # tear down ``` - -### Component versions (pinned in `scripts/install.sh`) - -| Component | Version | -|--------------|----------| -| Cilium CLI | v0.18.8 | -| cert-manager | v1.19.1 | -| Multus CNI | v4.2.3 | -| CNI plugins | v1.8.0 | -| Galactic | v0.0.5 | diff --git a/lab/gvpc/Makefile b/lab/gvpc/Makefile index ce953b7..26a984b 100644 --- a/lab/gvpc/Makefile +++ b/lab/gvpc/Makefile @@ -2,6 +2,9 @@ LAB_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) LAB := $(shell awk '/^name:/ {print $$2; exit}' *.clab.yaml) TOPO := $(wildcard *.clab.yaml) +KINDEST_VER := v1.35.1 +ARCH := $(shell uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') + .PHONY: help build up down reload inspect graph host-setup underlay overlay clean .DEFAULT_GOAL := help @@ -12,7 +15,14 @@ help: ## Show available targets | sort build: ## Build container images (Kind node) - docker build -t kindest/node:galactic $(LAB_DIR)containers/kindest-node-galactic + @if ! docker image inspect kindest/node:$(KINDEST_VER) >/dev/null 2>&1; then \ + crane pull --platform linux/$(ARCH) \ + kindest/node:$(KINDEST_VER) /tmp/kindest-node-$(KINDEST_VER).tar; \ + docker load -i /tmp/kindest-node-$(KINDEST_VER).tar; \ + rm /tmp/kindest-node-$(KINDEST_VER).tar; \ + fi + docker build --pull=false --build-arg KINDEST_VER=$(KINDEST_VER) \ + -t kindest/node:galactic $(LAB_DIR)containers/kindest-node-galactic up: build host-setup ## Build the Kind node image and deploy the lab sudo containerlab deploy -t $(TOPO) diff --git a/lab/gvpc/README.md b/lab/gvpc/README.md index ea68c89..4e8c640 100644 --- a/lab/gvpc/README.md +++ b/lab/gvpc/README.md @@ -132,6 +132,8 @@ gvpc/ │ └── tr4/ frr.conf startup.sh ├── group_files/ │ ├── common/ hosts vtysh.conf startup-lib.sh +│ ├── control/ daemons +│ ├── pe/ daemons │ └── transit/ daemons └── scripts/ ├── host-setup.sh @@ -166,7 +168,7 @@ make overlay # apply GoBGP DaemonSets to iad and sjc clusters | `graph` | Generate a draw.io diagram for the topology | | `host-setup` | Apply required host sysctls (IPv6 forwarding etc.) | | `underlay` | Apply FRR DaemonSets to all three clusters | -| `overlay` | Apply GoBGP DaemonSets to iad and sjc clusters | +| `overlay` | Pull GoBGP image, load into clusters, apply DaemonSets | | `clean` | Destroy lab, remove state, and delete the Kind node image | ## Verification diff --git a/lab/gvpc/containers/Makefile b/lab/gvpc/containers/Makefile deleted file mode 100644 index 32fc829..0000000 --- a/lab/gvpc/containers/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -.PHONY: help build pull-base create host-setup status - -.DEFAULT_GOAL := help - -KINDEST_VER := v1.35.1 -ARCH := $(shell uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') - -help: ## Show available targets - @grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) \ - | awk 'BEGIN {FS = ":.*##"}; {printf " \033[36m%-24s\033[0m %s\n", $$1, $$2}' \ - | sort - -pull-base: ## Pull the base Kind node image (uses crane; Docker Hub TLS 1.3 panics in OrbStack's msft-golang Docker builds) - @if ! docker image inspect kindest/node:$(KINDEST_VER) >/dev/null 2>&1; then \ - crane pull --platform linux/$(ARCH) \ - kindest/node:$(KINDEST_VER) /tmp/kindest-node-$(KINDEST_VER).tar; \ - docker load -i /tmp/kindest-node-$(KINDEST_VER).tar; \ - rm /tmp/kindest-node-$(KINDEST_VER).tar; \ - fi - -build: pull-base ## Build the Galactic image - docker build --pull=false -t kindest/node:galactic ./kindest-node-galactic/ - -create: ## Create the kind cluster - @if kind get clusters 2>/dev/null | grep -q '^kind$$'; then \ - echo "kind cluster already exists, skipping create"; \ - else \ - kind create cluster --config kind-config.yaml; \ - fi - kind export kubeconfig - -host-setup: ## Setup each node in the cluster - for node in $$(kind get nodes); do \ - docker exec "$$node" /galactic/scripts/install.sh; \ - done - -status: ## Show cluster nodes, pods, deployments, and services - @echo "\n=== Nodes ==="; \ - kubectl get nodes -o wide; \ - echo "\n=== Pods ==="; \ - kubectl get pods -A -o wide | egrep '^galactic|^NAMESPACE'; \ - echo "\n=== Deployments ==="; \ - kubectl get deployments -A | egrep '^galactic|^NAMESPACE'; \ - echo "\n=== Services ==="; \ - kubectl get services -A | egrep '^galactic|^NAMESPACE'; - -clean: ## Delete kind cluster - kind delete cluster diff --git a/lab/gvpc/containers/kindest-node-galactic/Dockerfile b/lab/gvpc/containers/kindest-node-galactic/Dockerfile index 1b959ac..55c38af 100644 --- a/lab/gvpc/containers/kindest-node-galactic/Dockerfile +++ b/lab/gvpc/containers/kindest-node-galactic/Dockerfile @@ -1,4 +1,6 @@ -FROM kindest/node:v1.35.1 +ARG KINDEST_VER=v1.35.1 +# ARG is assigned a default value, but we pass the desired one from the Makefile +FROM kindest/node:${KINDEST_VER} SHELL ["/bin/bash", "-eo", "pipefail", "-c"] @@ -10,7 +12,6 @@ RUN apt-get update \ WORKDIR /galactic COPY --chmod=0755 scripts/ ./scripts/ -COPY resources/ ./resources/ # Wrap kubectl to work around OrbStack DooD: kind runs `kubectl apply` for the # StorageClass step immediately after kubeadm init, but the apiserver briefly diff --git a/lab/gvpc/gvpc.clab.yaml b/lab/gvpc/gvpc.clab.yaml index 0821c2d..f4e0069 100644 --- a/lab/gvpc/gvpc.clab.yaml +++ b/lab/gvpc/gvpc.clab.yaml @@ -31,7 +31,7 @@ topology: extras: k8s_kind: deploy: - wait: 60s + wait: 0s kubeconfig: iad.kubeconfig iad-control-plane: @@ -86,7 +86,7 @@ topology: extras: k8s_kind: deploy: - wait: 60s + wait: 0s kubeconfig: sjc.kubeconfig sjc-control-plane: @@ -141,7 +141,7 @@ topology: extras: k8s_kind: deploy: - wait: 60s + wait: 0s kubeconfig: infra.kubeconfig infra-control-plane: From 47e794df162d6ef5046613d4ee3f5a5c862c3aae Mon Sep 17 00:00:00 2001 From: Michael Costello Date: Thu, 21 May 2026 20:44:43 +0000 Subject: [PATCH 6/8] fix(.devcontainer): mount gitconfig to /home/vscode and suppress port forwarding - corrects the gitconfig bind target from /root/.gitconfig to /home/vscode/.gitconfig (matching the non-root user) - adds otherPortsAttributes to suppress noisy auto-forward prompts. --- .devcontainer/containerlab-dood/devcontainer-lock.json | 9 +++++++++ .devcontainer/containerlab-dood/devcontainer.json | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/containerlab-dood/devcontainer-lock.json diff --git a/.devcontainer/containerlab-dood/devcontainer-lock.json b/.devcontainer/containerlab-dood/devcontainer-lock.json new file mode 100644 index 0000000..94b2b7f --- /dev/null +++ b/.devcontainer/containerlab-dood/devcontainer-lock.json @@ -0,0 +1,9 @@ +{ + "features": { + "ghcr.io/devcontainers/features/go:1": { + "version": "1.3.4", + "resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032", + "integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032" + } + } +} diff --git a/.devcontainer/containerlab-dood/devcontainer.json b/.devcontainer/containerlab-dood/devcontainer.json index 790f66c..86b717d 100644 --- a/.devcontainer/containerlab-dood/devcontainer.json +++ b/.devcontainer/containerlab-dood/devcontainer.json @@ -9,7 +9,7 @@ "type=bind,src=/run/docker/netns,dst=/run/docker/netns", "type=bind,src=/var/lib/docker,dst=/var/lib/docker", "type=bind,src=/lib/modules,dst=/lib/modules", - "source=${localEnv:HOME}/.gitconfig,target=/root/.gitconfig,type=bind,consistency=cached" + "source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached" ], "workspaceFolder": "${localWorkspaceFolder}", "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", @@ -18,6 +18,9 @@ "version": "1.24.5" } }, + "otherPortsAttributes": { + "onAutoForward": "ignore" + }, "customizations": { "vscode": { "extensions": [ From df3516dd83cc644d2f90a573e91a02132de5b779 Mon Sep 17 00:00:00 2001 From: Michael Costello Date: Fri, 22 May 2026 15:17:34 +0000 Subject: [PATCH 7/8] fix(.devcontainer): arm64 support, Ubuntu 24.04, and gitconfig mount fixes - Add multi-arch detection to containerlab-dood post-create (kubectl, crane) - Add Node.js devcontainer feature to containerlab-dood for Claude Code install - Switch galactic base image from ubuntu-22.04 to ubuntu-24.04 - Fix gitconfig bind mount in galactic: mount as .gitconfig.host (readonly) and copy on post-create to avoid EBUSY when VS Code writes its credential helper - Silence port auto-forwarding for metrics/health/webhook ports - Remove stale GO111MODULE=on and safe.directory git config calls Co-Authored-By: Claude Sonnet 4.6 --- .../containerlab-dood/devcontainer-lock.json | 5 +++ .../containerlab-dood/devcontainer.json | 6 ++- .../containerlab-dood/post-create.sh | 34 ++++++++-------- .devcontainer/galactic/devcontainer.json | 13 ++++--- .devcontainer/galactic/post-create.sh | 39 +++++++++++-------- 5 files changed, 59 insertions(+), 38 deletions(-) diff --git a/.devcontainer/containerlab-dood/devcontainer-lock.json b/.devcontainer/containerlab-dood/devcontainer-lock.json index 94b2b7f..75438be 100644 --- a/.devcontainer/containerlab-dood/devcontainer-lock.json +++ b/.devcontainer/containerlab-dood/devcontainer-lock.json @@ -4,6 +4,11 @@ "version": "1.3.4", "resolved": "ghcr.io/devcontainers/features/go@sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032", "integrity": "sha256:d85e921f91b41340055bb12b325d9d551170ed04b3b832e33530bf42f167c032" + }, + "ghcr.io/devcontainers/features/node:2": { + "version": "2.0.0", + "resolved": "ghcr.io/devcontainers/features/node@sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f", + "integrity": "sha256:fedd4c11f7adfb64283b578dddc7da906728daa25fa293351c9d913231acf12f" } } } diff --git a/.devcontainer/containerlab-dood/devcontainer.json b/.devcontainer/containerlab-dood/devcontainer.json index 86b717d..70d567c 100644 --- a/.devcontainer/containerlab-dood/devcontainer.json +++ b/.devcontainer/containerlab-dood/devcontainer.json @@ -1,4 +1,5 @@ { + "name": "containerlab-dood", "image": "ghcr.io/srl-labs/containerlab/devcontainer-dood-slim:0.75.0", "runArgs": [ "--network=host", @@ -9,13 +10,16 @@ "type=bind,src=/run/docker/netns,dst=/run/docker/netns", "type=bind,src=/var/lib/docker,dst=/var/lib/docker", "type=bind,src=/lib/modules,dst=/lib/modules", - "source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached" + "source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached,readonly" ], "workspaceFolder": "${localWorkspaceFolder}", "workspaceMount": "source=${localWorkspaceFolder},target=${localWorkspaceFolder},type=bind", "features": { "ghcr.io/devcontainers/features/go:1": { "version": "1.24.5" + }, + "ghcr.io/devcontainers/features/node:2": { + "version": "lts" } }, "otherPortsAttributes": { diff --git a/.devcontainer/containerlab-dood/post-create.sh b/.devcontainer/containerlab-dood/post-create.sh index b326b1f..3322958 100755 --- a/.devcontainer/containerlab-dood/post-create.sh +++ b/.devcontainer/containerlab-dood/post-create.sh @@ -1,46 +1,48 @@ #!/usr/bin/env bash set -euo pipefail -echo "Starting post-create setup for Galactic containerlab environment..." +echo "Starting post-create setup for containerlab-dood environment..." -# Install packages -echo "Installing Debian packages..." +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ARCH=$(uname -m) +case "$ARCH" in + x86_64) ARCH="amd64" ;; + aarch64) ARCH="arm64" ;; +esac + +# Upgrade/install packages +echo "Upgrading/installing Debian packages..." sudo apt update sudo DEBIAN_FRONTEND=noninteractive apt upgrade -y -# Install Claude -curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - -sudo apt install -y nodejs -sudo npm install -g @anthropic-ai/claude-code + +# Install Claude Code +echo "Installing Claude Code..." +curl -fsSL https://claude.ai/install.sh | bash # Install kubectl echo "Installing kubectl..." KUBECTL_VERSION=$(curl -fsSL https://dl.k8s.io/release/stable.txt) -curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/arm64/kubectl" \ +curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/${ARCH}/kubectl" \ | sudo tee /usr/local/bin/kubectl > /dev/null sudo chmod +x /usr/local/bin/kubectl # Install kind for local Kubernetes echo "Installing Kind..." -GO111MODULE=on go install sigs.k8s.io/kind@latest +go install sigs.k8s.io/kind@latest # Install crane for pulling container images (Docker binaries in this environment # panic on TLS 1.3 to Docker Hub due to an msft-golang/OpenSSL bug on ARM64) echo "Installing crane..." CRANE_VERSION=$(curl -fsSL https://api.github.com/repos/google/go-containerregistry/releases/latest | grep '"tag_name"' | cut -d'"' -f4) -curl -fsSL "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_arm64.tar.gz" \ +curl -fsSL "https://github.com/google/go-containerregistry/releases/download/${CRANE_VERSION}/go-containerregistry_Linux_${ARCH}.tar.gz" \ | sudo tar xz -C /usr/local/bin crane -# Disable atuin's up-arrow TUI (keep atuin for ctrl-r but restore normal up-arrow history) -sed -i 's/eval "$(atuin init zsh)"/eval "$(atuin init zsh --disable-up-arrow)"/' ~/.zshrc - # Verify installations echo "" echo "Verifying installations..." echo "Go version: $(go version)" -echo "Python version: $(python3 --version)" -echo "kubectl version: $(kubectl version --client --short 2>/dev/null || echo 'kubectl not installed')" +echo "kubectl version: $(kubectl version --client 2>/dev/null || echo 'kubectl not installed')" echo "kind version: $(kind version)" -echo "kustomize version: $(kustomize version --short 2>/dev/null || echo 'kustomize not installed')" echo "Docker version: $(docker --version)" echo "" diff --git a/.devcontainer/galactic/devcontainer.json b/.devcontainer/galactic/devcontainer.json index df49f0a..a1944ac 100644 --- a/.devcontainer/galactic/devcontainer.json +++ b/.devcontainer/galactic/devcontainer.json @@ -1,6 +1,6 @@ { "name": "galactic", - "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04", + "image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04", "features": { "ghcr.io/devcontainers/features/go:1": { "version": "1.24.2" @@ -111,17 +111,20 @@ "portsAttributes": { "8080": { "label": "Metrics", - "onAutoForward": "notify" + "onAutoForward": "silent" }, "8081": { "label": "Health", - "onAutoForward": "notify" + "onAutoForward": "silent" }, "9443": { "label": "Webhook", - "onAutoForward": "notify" + "onAutoForward": "silent" } }, + "otherPortsAttributes": { + "onAutoForward": "ignore" + }, "remoteUser": "vscode", "containerEnv": { "GOOS": "linux", @@ -129,7 +132,7 @@ }, "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind", - "source=${localEnv:HOME}${localEnv:USERPROFILE}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached" + "source=${localEnv:HOME}/.gitconfig,target=/home/vscode/.gitconfig.host,type=bind,consistency=cached,readonly" ], "postCreateCommand": "bash .devcontainer/galactic/post-create.sh", "runArgs": [ diff --git a/.devcontainer/galactic/post-create.sh b/.devcontainer/galactic/post-create.sh index ee7da73..4360074 100755 --- a/.devcontainer/galactic/post-create.sh +++ b/.devcontainer/galactic/post-create.sh @@ -3,6 +3,14 @@ set -euo pipefail echo "Starting post-create setup for Galactic development environment..." +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Copy host gitconfig so VS Code can write its credential helper without hitting +# EBUSY (which occurs when the target path itself has an active bind mount). +if [[ -f /home/vscode/.gitconfig.host ]]; then + cp /home/vscode/.gitconfig.host /home/vscode/.gitconfig +fi + # Set up Go tools echo "Installing Go development tools..." go install golang.org/x/tools/gopls@latest @@ -16,29 +24,32 @@ make controller-gen kustomize setup-envtest # Install kind for local Kubernetes testing echo "Installing Kind..." -GO111MODULE=on go install sigs.k8s.io/kind@latest +go install sigs.k8s.io/kind@latest # Install protoc (Protocol Buffer compiler) echo "Installing protoc..." PROTOC_VERSION="25.1" -curl -LO "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip" -sudo unzip -o protoc-${PROTOC_VERSION}-linux-x86_64.zip -d /usr/local bin/protoc -sudo unzip -o protoc-${PROTOC_VERSION}-linux-x86_64.zip -d /usr/local 'include/*' -rm -f protoc-${PROTOC_VERSION}-linux-x86_64.zip +PROTOC_ARCH=$(uname -m) +# protoc releases use aarch_64 (with underscore) for ARM64, unlike most other tools +case "$PROTOC_ARCH" in aarch64) PROTOC_ARCH="aarch_64" ;; esac +PROTOC_ZIP="protoc-${PROTOC_VERSION}-linux-${PROTOC_ARCH}.zip" +curl -fsSLO "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/${PROTOC_ZIP}" +sudo unzip -o "${PROTOC_ZIP}" -d /usr/local bin/protoc +sudo unzip -o "${PROTOC_ZIP}" -d /usr/local 'include/*' +rm -f "${PROTOC_ZIP}" # Install protoc-gen-go for Go protobuf generation echo "Installing protoc-gen-go..." go install google.golang.org/protobuf/cmd/protoc-gen-go@latest go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest -# Install network tools -echo "Installing network tools..." -sudo apt update -sudo apt install -y software-properties-common +# Upgrade/install packages +echo "Upgrading/installing Ubuntu packages..." +sudo apt-get update -q +sudo apt-get install -y -q software-properties-common sudo add-apt-repository -y ppa:apt-fast/stable -sudo apt update -sudo DEBIAN_FRONTEND=noninteractive apt install -y apt-fast -sudo apt-fast upgrade -y +sudo apt-get update -q +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -q apt-fast sudo apt-fast install -y \ iproute2 \ iptables \ @@ -65,10 +76,6 @@ echo "Generating Kubernetes manifests and DeepCopy methods..." cd /workspaces/galactic make manifests generate -# Set up git safe directory -echo "Configuring git safe directory..." -git config --add safe.directory /workspaces/galactic - # Install Claude Code CLI echo "Installing Claude Code..." curl -fsSL https://claude.ai/install.sh | bash From a4e88b483e72616322c1a98de199d9b5e101bf16 Mon Sep 17 00:00:00 2001 From: Michael Costello Date: Fri, 22 May 2026 15:17:41 +0000 Subject: [PATCH 8/8] fix(lab/gvpc): correct IPv6 VPN AFI-SAFI and add test targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix iad and sjc overlay GoBGP config: l3vpn-ipv4-unicast → l3vpn-ipv6-unicast - Switch infra-control-plane FRR to ipv6 vpn address-family; add Null0 static route and network advertisement for the SRv6 /48 prefix - Fix kubectl-wrapper to place --server flag before $@ (flags must precede args) - Add make test targets: test-bgp-transit, test-bgp-underlay, test-srv6, test-l3vpn Co-Authored-By: Claude Sonnet 4.6 --- lab/gvpc/Makefile | 30 ++++++++++++++++++- .../kindest-node-galactic/kubectl-wrapper | 2 +- lab/gvpc/resources/iad-overlay.k8s.yaml | 2 +- .../resources/infra-control-plane.k8s.yaml | 5 +++- lab/gvpc/resources/sjc-overlay.k8s.yaml | 2 +- 5 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lab/gvpc/Makefile b/lab/gvpc/Makefile index 26a984b..56882a7 100644 --- a/lab/gvpc/Makefile +++ b/lab/gvpc/Makefile @@ -5,7 +5,8 @@ TOPO := $(wildcard *.clab.yaml) KINDEST_VER := v1.35.1 ARCH := $(shell uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') -.PHONY: help build up down reload inspect graph host-setup underlay overlay clean +.PHONY: help build up down reload inspect graph host-setup underlay overlay test test-bgp-transit test-bgp-underlay \ + test-srv6 test-l3vpn clean .DEFAULT_GOAL := help @@ -50,6 +51,33 @@ overlay: ## Pull GoBGP image, load into clusters, and apply DaemonSets to iad an kind load docker-image osrg/gobgp:latest --name sjc ./scripts/install-overlay.sh +test: test-bgp-transit test-bgp-underlay test-srv6 test-l3vpn + +test-bgp-transit: ## Verify transit BGP sessions routes + @echo "=== Transit router BGP (iBGP full mesh) ===" + @for r in tr1 tr2 tr3 tr4; do \ + echo "--- $$r ---"; \ + docker exec clab-gvpc-$$r vtysh -c "show bgp ipv6 unicast summary"; \ + done + +test-bgp-underlay: ## Verify underlay BGP sessions + @echo "=== Worker underlay eBGP ===" + docker exec iad-control-plane kubectl exec -n iad-underlay ds/iad-underlay -- vtysh -c "show bgp ipv6 unicast summary" + docker exec sjc-control-plane kubectl exec -n sjc-underlay ds/sjc-underlay -- vtysh -c "show bgp ipv6 unicast summary" + +test-srv6: ## Verify SRv6 routes + @echo "=== SRv6 forwarding prefixes on tr1 ===" + docker exec clab-gvpc-tr1 vtysh -c "show bgp ipv6 unicast 2001:db8:ff01::/48" + docker exec clab-gvpc-tr1 vtysh -c "show bgp ipv6 unicast 2001:db8:ff02::/48" + docker exec clab-gvpc-tr1 vtysh -c "show bgp ipv6 unicast 2001:db8:ff03::/48" + +test-l3vpn: ## GoBGP L3VPN + @echo "=== GoBGP L3VPN neighbors and RIB ===" + docker exec iad-control-plane kubectl exec -n iad-overlay ds/iad-overlay -- gobgp neighbor + docker exec sjc-control-plane kubectl exec -n sjc-overlay ds/sjc-overlay -- gobgp neighbor + docker exec iad-control-plane kubectl exec -n iad-overlay ds/iad-overlay -- gobgp global rib -a vpnv6 + docker exec sjc-control-plane kubectl exec -n sjc-overlay ds/sjc-overlay -- gobgp global rib -a vpnv6 + clean: down ## Destroy the lab, remove ContainerLab state, and delete container images docker rmi kindest/node:galactic || true docker rmi osrg/gobgp:latest || true diff --git a/lab/gvpc/containers/kindest-node-galactic/kubectl-wrapper b/lab/gvpc/containers/kindest-node-galactic/kubectl-wrapper index 929a025..6f80184 100644 --- a/lab/gvpc/containers/kindest-node-galactic/kubectl-wrapper +++ b/lab/gvpc/containers/kindest-node-galactic/kubectl-wrapper @@ -6,4 +6,4 @@ until curl -sk https://127.0.0.1:6443/healthz >/dev/null 2>&1; do sleep 1 done -exec /usr/bin/kubectl "$@" --server=https://127.0.0.1:6443 +exec /usr/bin/kubectl --server=https://127.0.0.1:6443 "$@" diff --git a/lab/gvpc/resources/iad-overlay.k8s.yaml b/lab/gvpc/resources/iad-overlay.k8s.yaml index 348e015..3c7fbf0 100644 --- a/lab/gvpc/resources/iad-overlay.k8s.yaml +++ b/lab/gvpc/resources/iad-overlay.k8s.yaml @@ -27,7 +27,7 @@ data: local-address = "fc00:0:2::1" [[neighbors.afi-safis]] [neighbors.afi-safis.config] - afi-safi-name = "l3vpn-ipv4-unicast" + afi-safi-name = "l3vpn-ipv6-unicast" --- apiVersion: apps/v1 diff --git a/lab/gvpc/resources/infra-control-plane.k8s.yaml b/lab/gvpc/resources/infra-control-plane.k8s.yaml index b9c642c..d944018 100644 --- a/lab/gvpc/resources/infra-control-plane.k8s.yaml +++ b/lab/gvpc/resources/infra-control-plane.k8s.yaml @@ -47,6 +47,8 @@ data: description tr3-facing ! + ipv6 route 2001:db8:ff03::/48 Null0 + ! router bgp 65000 bgp router-id 10.255.255.1 no bgp default ipv4-unicast @@ -63,10 +65,11 @@ data: address-family ipv6 unicast neighbor eth1 activate neighbor eth1 allowas-in 1 + network 2001:db8:ff03::/48 network fc00:0:4::1/128 exit-address-family ! - address-family ipv4 vpn + address-family ipv6 vpn neighbor fc00:0:2::1 activate neighbor fc00:0:2::1 route-reflector-client neighbor fc00:0:3::1 activate diff --git a/lab/gvpc/resources/sjc-overlay.k8s.yaml b/lab/gvpc/resources/sjc-overlay.k8s.yaml index 84a5188..98d720f 100644 --- a/lab/gvpc/resources/sjc-overlay.k8s.yaml +++ b/lab/gvpc/resources/sjc-overlay.k8s.yaml @@ -27,7 +27,7 @@ data: local-address = "fc00:0:3::1" [[neighbors.afi-safis]] [neighbors.afi-safis.config] - afi-safi-name = "l3vpn-ipv4-unicast" + afi-safi-name = "l3vpn-ipv6-unicast" --- apiVersion: apps/v1