| name | fabric-containers |
| description | Deploy Docker containers and Kubernetes clusters on FABRIC nodes |
| allowed-tools | ["Read","Grep","Glob","Write","Edit","Bash"] |
Instructions
When invoked, generate code for container-based workloads on FABRIC. Two main capabilities:
- Docker — Container deployment using post-boot scripts or runtime commands
- Kubernetes — Multi-node K8s cluster with Flannel CNI across FABRIC sites
Help the user choose the right approach and generate complete setup code.
API Reference
Docker Images
FABRIC provides Docker-ready OS images:
docker_rocky_8 — Rocky Linux 8 with Docker pre-configured
docker_ubuntu_20 — Ubuntu 20.04 with Docker pre-configured
Post-Boot Docker Setup
node.add_post_boot_upload_directory('node_tools', '.')
node.add_post_boot_execute('node_tools/enable_docker.sh {{ _self_.image }}')
Post-Boot Docker Compose
node.add_post_boot_upload_directory('docker_containers', '.')
node.add_post_boot_execute('cd docker_containers/my_app ; docker compose up -d')
Patterns
Docker Container Deployment
from fabrictestbed_extensions.fablib.fablib import FablibManager
fablib = FablibManager()
slice = fablib.new_slice(name="docker-experiment")
node = slice.add_node(
name="docker-node",
site=fablib.get_random_site(),
cores=4,
ram=16,
disk=100,
image="docker_rocky_8",
)
node.add_fabnet()
node.add_post_boot_upload_directory('node_tools', '.')
node.add_post_boot_execute('node_tools/enable_docker.sh {{ _self_.image }}')
slice.submit()
node = slice.get_node(name="docker-node")
stdout, stderr = node.execute(
"docker run -d -it "
"--name my_container "
"fabrictestbed/slice-vm-rocky8-multitool:0.0.1"
)
stdout, stderr = node.execute("docker ps -a")
print(stdout)
stdout, stderr = node.execute("docker exec my_container ip addr list")
print(stdout)
Docker Compose via Post-Boot
slice = fablib.new_slice(name="compose-experiment")
node = slice.add_node(
name="app-node",
site=fablib.get_random_site(),
cores=4,
ram=16,
disk=100,
image="docker_rocky_8",
)
node.add_post_boot_upload_directory('node_tools', '.')
node.add_post_boot_execute('node_tools/enable_docker.sh {{ _self_.image }}')
node.add_post_boot_upload_directory('docker_containers', '.')
node.add_post_boot_execute('cd docker_containers/my_app ; docker compose up -d')
slice.submit()
Kubernetes Cluster (Multi-Site)
from fabrictestbed_extensions.fablib.fablib import FablibManager
fablib = FablibManager()
[site1, site2] = fablib.get_random_sites(count=2)
slice = fablib.new_slice(name="k8s-cluster")
net1 = slice.add_l3network(name="net1", type="IPv4")
net2 = slice.add_l3network(name="net2", type="IPv4")
ctrlr = slice.add_node(
name="controller",
site=site1,
cores=8,
ram=32,
disk=100,
image="default_ubuntu_22",
)
iface = ctrlr.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface.set_mode("auto")
net1.add_interface(iface)
ctrlr.add_route(subnet=fablib.FABNETV4_SUBNET, next_hop=net1.get_gateway())
worker1 = slice.add_node(
name="worker1",
site=site1,
cores=8,
ram=32,
disk=100,
image="default_ubuntu_22",
)
iface1 = worker1.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface1.set_mode("auto")
net1.add_interface(iface1)
worker1.add_route(subnet=fablib.FABNETV4_SUBNET, next_hop=net1.get_gateway())
worker2 = slice.add_node(
name="worker2",
site=site2,
cores=8,
ram=32,
disk=100,
image="default_ubuntu_22",
)
iface2 = worker2.add_component(model="NIC_Basic", name="nic1").get_interfaces()[0]
iface2.set_mode("auto")
net2.add_interface(iface2)
worker2.add_route(subnet=fablib.FABNETV4_SUBNET, next_hop=net2.get_gateway())
slice.submit()
SSH Key Distribution (For K8s)
nodes = slice.get_nodes()
for n in nodes:
n.execute('ssh-keygen -t rsa -N "" -f /home/ubuntu/.ssh/id_rsa', quiet=True)
n.execute('sudo ssh-keygen -t rsa -N "" -f /root/.ssh/id_rsa', quiet=True)
keys = {}
for n in nodes:
ubuntu_key, _ = n.execute("cat /home/ubuntu/.ssh/id_rsa.pub", quiet=True)
root_key, _ = n.execute("sudo cat /root/.ssh/id_rsa.pub", quiet=True)
keys[n.get_name()] = {"ubuntu": ubuntu_key.strip(), "root": root_key.strip()}
for n in nodes:
for other_name, node_keys in keys.items():
if other_name == n.get_name():
continue
n.execute(f'echo "{node_keys["ubuntu"]}" >> /home/ubuntu/.ssh/authorized_keys')
n.execute(
f"sudo sh -c 'echo \"{node_keys['root']}\" >> /root/.ssh/authorized_keys'"
)
Configure /etc/hosts (For K8s)
node_ips = {}
for n in nodes:
for iface in n.get_interfaces():
ip = iface.get_ip_addr()
if ip:
node_ips[n.get_name()] = str(ip)
break
for n in nodes:
for name, ip in node_ips.items():
n.execute(f"sudo sh -c 'echo \"{ip} {name}\" >> /etc/hosts'")
Install Kubernetes Components
for n in nodes:
n.upload_directory(local_directory_path="./node_tools", remote_directory_path=".")
for n in nodes:
n.execute(
"sudo ./node_tools/k8s_pre_install.sh",
quiet=True,
output_file=f"logs/{n.get_name()}_pre_install.log",
)
slice.wait_ssh()
for n in nodes:
n.config()
for n in nodes:
n.execute(
"sudo ./node_tools/k8s_install.sh",
quiet=True,
output_file=f"logs/{n.get_name()}_install.log",
)
Initialize K8s Control Plane
ctrlr = slice.get_node(name="controller")
ctrlr_addr = node_ips["controller"]
ctrlr.execute(f"sudo node_tools/k8s_kubelet.sh {ctrlr_addr}")
ctrlr.execute(f"node_tools/k8s_init_control_plane.sh {ctrlr_addr}")
ctrlr.execute(f"node_tools/k8s_flannel.sh {ctrlr_addr}")
stdout, _ = ctrlr.execute("node_tools/k8s_status.sh")
print(stdout)
Join Worker Nodes
join_command, _ = ctrlr.execute("kubeadm token create --print-join-command")
for n in nodes:
if n.get_name() == "controller":
continue
n.execute(f"sudo {join_command.strip()}")
stdout, _ = ctrlr.execute("kubectl get nodes -o wide")
print(stdout)
Deploy Application
ctrlr.execute("kubectl apply -f node_tools/pod.yml")
ctrlr.execute("kubectl apply -f node_tools/service_nodeport.yml")
stdout, _ = ctrlr.execute("kubectl get pods -o wide")
print(stdout)
SSH Tunnel to Access K8s Services
import os
local_port = "30080"
target_host = f"{ctrlr.get_username()}@{ctrlr.get_management_ip()}"
key_file = os.path.basename(fablib.get_default_slice_public_key_file())[:-4]
print(
f"ssh -L 127.0.0.1:{local_port}:127.0.0.1:{local_port} "
f"-i {key_file} -F ssh_config {target_host}"
)
print(f"Then browse: http://127.0.0.1:{local_port}")
Important Notes
- Docker images: Use
docker_rocky_8 or docker_ubuntu_20 for pre-configured Docker
- enable_docker.sh: Uses
{{ _self_.image }} template variable for OS detection
- K8s networking: Use L3/FABNetv4 for cross-site pod communication
- Flannel CNI: Must be patched for FABRIC's IPv6 management network
- After reboot: Always call
slice.wait_ssh() then node.config() to restore networking
- CPU pinning: Use
node.pin_cpu() and node.numa_tune() for performance-sensitive workloads