# Plumbr on K8s between two AWS Clusters Let's run plumbr as a sidecar and send the tun traffic over a macvlan interface ### General gist is... * Create a secondary pod interface for macvlan ("net1" interface) * Plumr runs in an init container and creates a tun with IP addressing related to the macvlan interface * This is a kind of "side car" type approach, and the plumr binary runs as a background process ### Requirements * Two OpenShift clusters on AWS with Multus CNI installed (and operational). * Must have connectivity on bridges over a VPC (as setup by Ali in my case) ### Limitations * Uses statically defined IP addressing * And pods pinned to specific nodes. * This is for a single 1:1 point-to-point connection. * As opposed to a many:1 or 1:many. * Further enhancement will require a form of service discovery. * Plumr is run as a side car "manually" * That is, with a yaml specification for it. * This could be improved to be automated (addressing, too). * Has rather loose security restrictions for the pod itself * This can be addressed with CNI + a controller. * This uses bridge for connectivity * Unsure about performance impact. * If the plumr binary fails, the pod will keep running * This could be improved with a controller as well. ## Setup Copy the `plumr` and `shm_printer` binary to each host. These examples use a path @ `/tmp/plumr/` where the binary is copied. This is a manual step required as this binary is not publicly available (e.g. in a container image, which would save this step). *NOTE*: I've typically been putting assets in the `vlc` namespace, so `oc project vlc` to change to it. ### Connectivity In Ali's cluster the bridging across the VPC works by subnet, and it's setup like this... ``` 10.10.0.0/16 - US 10.10.1.0/24 - US (worker 1) 10.10.2.0/24 - US (worker 2) 10.20.0.0/16 - APAC 10.20.1.0/24 - apac (worker 1) 10.20.2.0/24 - apac (worker 2) ``` In the examples I have the pods with the IP addresses of... ``` 10.10.1.200 == Ohio 10.20.1.200 == Mumbai ``` In order to determine which node to use for which, you can log into the bastion host and using the `Mumbai` or `Ohio` directories, look at the deployment yaml, for example... In `/home/rosa/Mumbai`... ``` [rosa@bastion Mumbai]$ cat *w1* | grep -P "range|nodeSelector|kubernetes.io/hostname" nodeSelector: kubernetes.io/hostname: ip-10-1-241-95.ap-south-1.compute.internal "range": "10.20.1.0/24", nodeSelector: kubernetes.io/hostname: ip-10-1-241-95.ap-south-1.compute.internal nodeSelector: kubernetes.io/hostname: ip-10-1-241-95.ap-south-1.compute.internal ``` Here we can see that the range `10.20.1.0/24` is associated with the host `ip-10-1-241-95.ap-south-1.compute.internal` *IMPORTANT*: You must change the `kubernetes.io/hostname` and the IP addressing to match. ## Inspecting the plumr enabled pods You'll create the resources using the examples that follow in the following sections. Then, you'll want to inspect the pods... Here's an example where we exec into one of the plumr pods. ``` $ oc exec -it plumbr-example-b -c workload -- /bin/bash ``` You can look at the configuration used for plumr with: ``` [root@plumbr-example-b /]# cat /shared-data/config.json ``` And you can inspect the logs from plumr (typically: "basically nothing" is good in my experience) ``` [root@plumbr-example-b /]# cat /shared-data/entrypoint.log templating json... running plumr... ``` You can run a test ping to the other side over the bridge... ``` [root@plumbr-example-b /]# ping -c 2 10.10.1.200 PING 10.10.1.200 (10.10.1.200) 56(84) bytes of data. 64 bytes from 10.10.1.200: icmp_seq=1 ttl=59 time=199 ms 64 bytes from 10.10.1.200: icmp_seq=2 ttl=59 time=197 ms ``` And inspect the available interfaces... ``` [root@plumbr-example-b /]# ip a | grep -P "^\d" 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 2: eth0@if61: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8901 qdisc noqueue state UP group default 3: net1@if62: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 4: tun: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1452 qdisc fq_codel state UNKNOWN group default qlen 500 ``` Note: The iperf3 binary is available. ``` [root@plumbr-example-b /]# iperf3 --version iperf 3.14 (cJSON 1.7.15) ``` Also to run the `shm_printer`, execute... ``` [root@plumbr-example-a /]# /plumrbin/shm_printer --name plumr_metrics Start reader Configure Waiting for producer with name plumr_metrics ``` ## Resources for Ohio-side cluster ```yaml= apiVersion: v1 kind: ConfigMap metadata: name: plumbr-entrypoint-config data: plumbr-entrypoint.sh: | #!/bin/sh TUN_IP="${TUN_IP:-10.4.0.1}" UDP_REMOTE_IP="${UDP_REMOTE_IP:-192.168.122.111}" JSON_TEMPLATE='{ "pipeline": [ { "name": "tun", "tun": { "input": "tun_input", "output": "fragmentizer_input", "ip": "%s" } }, { "name": "fragmentizer", "fragmentizer": { "input": "fragmentizer_input", "output": "rely_encoder_input" } }, { "name": "defragmentizer", "defragmentizer": { "input": "defragmentizer_input", "output": "tun_input" } }, { "name": "rely_encoder", "relyEncoder": { "input": "rely_encoder_input", "output": "udp_input", "timeout": 100, "repair_interval": 1, "repair_target": 2, "flush_repair_timeout": 50 } }, { "name": "rely_decoder", "relyDecoder": { "input": "rely_decoder_input", "output": "defragmentizer_input", "timeout": 150, "release_in_order": true } }, { "name": "udp", "udp": { "input": "udp_input", "output": "rely_decoder_input", "remote_address": "%s:9999" } } ], "shm_config": { "name": "plumr-client", "bytes": 1000000, "interval_ms": 100 } }' echo "templating json..." JSON_CONTENT=$(printf "$JSON_TEMPLATE" "$TUN_IP" "$UDP_REMOTE_IP") echo "$JSON_CONTENT" > /shared-data/config.json echo "running plumr..." /plumrbin/plumr -f /shared-data/config.json sleep infinity --- apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: br-plumr-config spec: config: '{ "cniVersion": "0.3.0", "type": "bridge", "isGateway": true, "ipam": { "type": "static", "capabilites": { "ips": true }, "routes": [ { "dst": "10.10.2.0/24" }, { "dst": "10.20.0.0/16" } ] } }' --- apiVersion: v1 kind: Pod metadata: name: plumbr-example-a annotations: k8s.v1.cni.cncf.io/networks: '[ { "name": "br-plumr-config", "ips": [ "10.10.1.200/24" ] } ]' spec: shareProcessNamespace: true nodeSelector: kubernetes.io/hostname: ip-10-0-154-10.us-east-2.compute.internal initContainers: - name: run-plumr image: quay.io/dosmith/fedora-vlc-iptools:gen2 command: ["/bin/sh", "-c", "/entrypoint/plumbr-entrypoint.sh > /shared-data/entrypoint.log 2>&1 &"] env: - name: TUN_IP value: "10.4.0.1" - name: UDP_REMOTE_IP value: "10.20.1.200" volumeMounts: - name: host-bin mountPath: /plumrbin/ - name: plumbr-entrypoint-volume mountPath: /entrypoint - name: shared-volume mountPath: /shared-data securityContext: privileged: true capabilities: add: ["NET_ADMIN","NET_RAW"] containers: - name: workload image: quay.io/dosmith/fedora-vlc-iptools:gen2 command: ["/bin/sh", "-c", "sleep 10000000000000000"] volumeMounts: - name: shared-volume mountPath: /shared-data - name: host-bin mountPath: /plumrbin/ securityContext: privileged: true volumes: - name: host-bin hostPath: path: /tmp/plumr type: Directory - name: plumbr-entrypoint-volume configMap: name: plumbr-entrypoint-config defaultMode: 0744 - name: shared-volume emptyDir: {} ``` # Resources for Mumbai-side cluster ```yaml= apiVersion: v1 kind: ConfigMap metadata: name: plumbr-entrypoint-config data: plumbr-entrypoint.sh: | #!/bin/sh TUN_IP="${TUN_IP:-10.4.0.1}" UDP_REMOTE_IP="${UDP_REMOTE_IP:-192.168.122.111}" JSON_TEMPLATE='{ "pipeline": [ { "name": "tun", "tun": { "input": "tun_input", "output": "fragmentizer_input", "ip": "%s" } }, { "name": "fragmentizer", "fragmentizer": { "input": "fragmentizer_input", "output": "rely_encoder_input" } }, { "name": "defragmentizer", "defragmentizer": { "input": "defragmentizer_input", "output": "tun_input" } }, { "name": "rely_encoder", "relyEncoder": { "input": "rely_encoder_input", "output": "udp_input", "timeout": 100, "repair_interval": 1, "repair_target": 2, "flush_repair_timeout": 50 } }, { "name": "rely_decoder", "relyDecoder": { "input": "rely_decoder_input", "output": "defragmentizer_input", "timeout": 150, "release_in_order": true } }, { "name": "udp", "udp": { "input": "udp_input", "output": "rely_decoder_input", "remote_address": "%s:9999" } } ], "shm_config": { "name": "plumr-client", "bytes": 1000000, "interval_ms": 100 } }' echo "templating json..." JSON_CONTENT=$(printf "$JSON_TEMPLATE" "$TUN_IP" "$UDP_REMOTE_IP") echo "$JSON_CONTENT" > /shared-data/config.json echo "running plumr..." /plumrbin/plumr -f /shared-data/config.json sleep infinity --- apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: br-plumr-config spec: config: '{ "cniVersion": "0.3.0", "type": "bridge", "isGateway": true, "ipam": { "type": "static", "capabilites": { "ips": true }, "routes": [ { "dst": "10.20.2.0/24" }, { "dst": "10.20.3.0/24" }, { "dst": "10.10.0.0/16" } ] } }' --- apiVersion: v1 kind: Pod metadata: name: plumbr-example-b annotations: k8s.v1.cni.cncf.io/networks: '[ { "name": "br-plumr-config", "ips": [ "10.20.1.200/24" ] } ]' spec: shareProcessNamespace: true nodeSelector: kubernetes.io/hostname: ip-10-1-236-160.ap-south-1.compute.internal initContainers: - name: run-plumr image: quay.io/dosmith/fedora-vlc-iptools:gen2 command: ["/bin/sh", "-c", "/entrypoint/plumbr-entrypoint.sh > /shared-data/entrypoint.log 2>&1 &"] env: - name: TUN_IP value: "10.4.0.2" - name: UDP_REMOTE_IP value: "10.10.1.200" volumeMounts: - name: host-bin mountPath: /plumrbin/ - name: plumbr-entrypoint-volume mountPath: /entrypoint - name: shared-volume mountPath: /shared-data securityContext: privileged: true capabilities: add: ["NET_ADMIN","NET_RAW"] containers: - name: workload image: quay.io/dosmith/fedora-vlc-iptools:gen2 command: ["/bin/sh", "-c", "sleep 10000000000000000"] volumeMounts: - name: shared-volume mountPath: /shared-data - name: host-bin mountPath: /plumrbin/ securityContext: privileged: true volumes: - name: host-bin hostPath: path: /tmp/plumr type: Directory - name: plumbr-entrypoint-volume configMap: name: plumbr-entrypoint-config defaultMode: 0744 - name: shared-volume emptyDir: {} ``` ### The `Dockerfile` for the `dougbtv/fedora-iptools` image ``` FROM registry.fedoraproject.org/fedora:38 RUN dnf install -y iproute iputils tcpdump iperf3 ``` ### The `Dockerfile` for the `dougbtv/fedora-vlc-iptools` image ``` FROM registry.fedoraproject.org/fedora:38 RUN dnf install -y iproute iputils tcpdump iperf3 procps-ng iproute-tc RUN yes | dnf install \ https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm RUN dnf install -y vlc ``` ### The `Dockerfile` for the `quay.io/dosmith/fedora-vlc-iptools-speedtest:gen1` image ``` FROM registry.fedoraproject.org/fedora:38 # Install PHP, Apache and the required extensions, plus the ip utils. RUN dnf -y install httpd php php-pdo php-pgsql php-mysqlnd \ php-gd php-pear php-cli php-pdo php-json \ git net-tools nano iproute iputils tcpdump iperf3 procps-ng iproute-tc htop # Clone Fatih's repo RUN git clone https://github.com/fenar/speedtest /var/www/html/speedtest RUN chown -R apache:apache /var/www/html/* # Set permissions RUN chown -R apache:apache /var/www/html/* # VLC install RUN yes | dnf install \ https://download1.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm RUN dnf install -y vlc # Expose port 80 EXPOSE 80 # Start Apache CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"] ``` ## Other... **WARNING**: These additional notes were created during debug and analysis and are left for reference. This was the config I saw modified on 10/4/23 ``` ==== plumbr-entrypoint.sh: ---- #!/bin/sh TUN_IP="${TUN_IP:-10.4.0.1}" UDP_REMOTE_IP="${UDP_REMOTE_IP:-192.168.122.111}" JSON_TEMPLATE='{ "pipeline": [ { "name": "tun", "tun": { "input": "tun_input", "output": "udp_input", "ip": "%s" } }, { "name": "udp", "udp": { "input": "udp_input", "output": "tun_input", "remote_address": "%s:9999" } } ], "shm_config": { "name": "plumr-client", "bytes": 1000000, "interval_ms": 100 } }' echo "templating json..." JSON_CONTENT=$(printf "$JSON_TEMPLATE" "$TUN_IP" "$UDP_REMOTE_IP") echo "$JSON_CONTENT" > /shared-data/config.json echo "running plumr..." /plumrbin/plumr -f /shared-data/config.json sleep infinity ``` ### Ohio Version 2 -- 10/4/23 ```yaml= apiVersion: v1 kind: ConfigMap metadata: name: plumbr-entrypoint-config data: plumbr-entrypoint.sh: | #!/bin/sh TUN_IP="${TUN_IP:-10.4.0.1}" UDP_REMOTE_IP="${UDP_REMOTE_IP:-192.168.122.111}" JSON_TEMPLATE='{ "pipeline": [ { "name": "tun", "tun": { "input": "tun_input", "output": "udp_input", "ip": "%s" } }, { "name": "udp", "udp": { "input": "udp_input", "output": "tun_input", "local_address": "%s:9999", "remote_address": "%s:9999" } } ], "shm_config": { "name": "plumr-client", "bytes": 1000000, "interval_ms": 100 } }' echo "templating json..." JSON_CONTENT=$(printf "$JSON_TEMPLATE" "$TUN_IP" "$UDP_LOCAL_IP" "$UDP_REMOTE_IP") echo "$JSON_CONTENT" > /shared-data/config.json echo "running plumr..." /plumrbin/plumr -f /shared-data/config.json sleep infinity --- apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: br-plumr-config spec: config: '{ "cniVersion": "0.3.0", "type": "bridge", "isGateway": true, "ipam": { "type": "static", "capabilites": { "ips": true }, "routes": [ { "dst": "10.10.2.0/24" }, { "dst": "10.20.0.0/16" } ] } }' --- apiVersion: v1 kind: Pod metadata: name: plumbr-example-a annotations: k8s.v1.cni.cncf.io/networks: '[ { "name": "br-plumr-config", "ips": [ "10.10.1.200/24" ] } ]' spec: shareProcessNamespace: true nodeSelector: kubernetes.io/hostname: ip-10-0-204-107.us-east-2.compute.internal initContainers: - name: run-plumr image: dougbtv/fedora-iptools:gen1 command: ["/bin/sh", "-c", "/entrypoint/plumbr-entrypoint.sh > /shared-data/entrypoint.log 2>&1 &"] env: - name: TUN_IP value: "10.4.0.1" - name: UDP_LOCAL_IP value: "10.10.1.200" - name: UDP_REMOTE_IP value: "10.20.1.200" volumeMounts: - name: host-bin mountPath: /plumrbin/ - name: plumbr-entrypoint-volume mountPath: /entrypoint - name: shared-volume mountPath: /shared-data securityContext: privileged: true capabilities: add: ["NET_ADMIN","NET_RAW"] containers: - name: workload image: dougbtv/fedora-iptools:gen2 command: ["/bin/sh", "-c", "sleep 10000000000000000"] volumeMounts: - name: shared-volume mountPath: /shared-data - name: host-bin mountPath: /plumrbin/ securityContext: privileged: true volumes: - name: host-bin hostPath: path: /tmp/plumr type: Directory - name: plumbr-entrypoint-volume configMap: name: plumbr-entrypoint-config defaultMode: 0744 - name: shared-volume emptyDir: {} ``` ### Ohio Interactive run This example does not run plumr, so that you can run it interactively. Exec into the pod and then you can... ``` $ vi /shared-data/config.json $ /plumrbin/plumr -f /shared-data/config.json ``` ```yaml= apiVersion: v1 kind: ConfigMap metadata: name: plumbr-entrypoint-config data: plumbr-entrypoint.sh: | #!/bin/sh TUN_IP="${TUN_IP:-10.4.0.1}" UDP_REMOTE_IP="${UDP_REMOTE_IP:-192.168.122.111}" JSON_TEMPLATE='{ "pipeline": [ { "name": "tun", "tun": { "input": "tun_input", "output": "udp_input", "ip": "%s" } }, { "name": "udp", "udp": { "input": "udp_input", "output": "tun_input", "local_address": "%s:9999", "remote_address": "%s:9999" } } ], "shm_config": { "name": "plumr-client", "bytes": 1000000, "interval_ms": 100 } }' echo "templating json..." JSON_CONTENT=$(printf "$JSON_TEMPLATE" "$TUN_IP" "$UDP_LOCAL_IP" "$UDP_REMOTE_IP") echo "$JSON_CONTENT" > /shared-data/config.json echo "Not running plumr..." echo "Run it with: /plumrbin/plumr -f /shared-data/config.json" sleep infinity --- apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: br-plumr-config spec: config: '{ "cniVersion": "0.3.0", "type": "bridge", "isGateway": true, "ipam": { "type": "static", "capabilites": { "ips": true }, "routes": [ { "dst": "10.10.2.0/24" }, { "dst": "10.20.0.0/16" } ] } }' --- apiVersion: v1 kind: Pod metadata: name: plumbr-example-a annotations: k8s.v1.cni.cncf.io/networks: '[ { "name": "br-plumr-config", "ips": [ "10.10.1.200/24" ] } ]' spec: shareProcessNamespace: true nodeSelector: kubernetes.io/hostname: ip-10-0-154-10.us-east-2.compute.internal initContainers: - name: run-plumr image: quay.io/dosmith/fedora-vlc-iptools:gen2 command: ["/bin/sh", "-c", "/entrypoint/plumbr-entrypoint.sh > /shared-data/entrypoint.log 2>&1 &"] env: - name: TUN_IP value: "10.4.0.1" - name: UDP_LOCAL_IP value: "10.10.1.200" - name: UDP_REMOTE_IP value: "10.20.1.200" volumeMounts: - name: host-bin mountPath: /plumrbin/ - name: plumbr-entrypoint-volume mountPath: /entrypoint - name: shared-volume mountPath: /shared-data securityContext: privileged: true capabilities: add: ["NET_ADMIN","NET_RAW"] containers: - name: workload image: quay.io/dosmith/fedora-vlc-iptools:gen2 command: ["/bin/sh", "-c", "sleep 10000000000000000"] volumeMounts: - name: shared-volume mountPath: /shared-data - name: host-bin mountPath: /plumrbin/ securityContext: privileged: true volumes: - name: host-bin hostPath: path: /tmp/plumr type: Directory - name: plumbr-entrypoint-volume configMap: name: plumbr-entrypoint-config defaultMode: 0744 - name: shared-volume emptyDir: {} ``` ### Mumbai Interactive run ```yaml= apiVersion: v1 kind: ConfigMap metadata: name: plumbr-entrypoint-config data: plumbr-entrypoint.sh: | #!/bin/sh TUN_IP="${TUN_IP:-10.4.0.1}" UDP_REMOTE_IP="${UDP_REMOTE_IP:-192.168.122.111}" JSON_TEMPLATE='{ "pipeline": [ { "name": "tun", "tun": { "input": "tun_input", "output": "udp_input", "ip": "%s" } }, { "name": "udp", "udp": { "input": "udp_input", "output": "tun_input", "local_address": "%s:9999", "remote_address": "%s:9999" } } ], "shm_config": { "name": "plumr-client", "bytes": 1000000, "interval_ms": 100 } }' echo "templating json..." JSON_CONTENT=$(printf "$JSON_TEMPLATE" "$TUN_IP" "$UDP_LOCAL_IP" "$UDP_REMOTE_IP") echo "$JSON_CONTENT" > /shared-data/config.json echo "Not running plumr..." echo "Run it with: /plumrbin/plumr -f /shared-data/config.json" sleep infinity --- apiVersion: "k8s.cni.cncf.io/v1" kind: NetworkAttachmentDefinition metadata: name: br-plumr-config spec: config: '{ "cniVersion": "0.3.0", "type": "bridge", "isGateway": true, "ipam": { "type": "static", "capabilites": { "ips": true }, "routes": [ { "dst": "10.20.2.0/24" }, { "dst": "10.20.3.0/24" }, { "dst": "10.10.0.0/16" } ] } }' --- apiVersion: v1 kind: Pod metadata: name: plumbr-example-a annotations: k8s.v1.cni.cncf.io/networks: '[ { "name": "br-plumr-config", "ips": [ "10.20.1.200/24" ] } ]' spec: shareProcessNamespace: true nodeSelector: kubernetes.io/hostname: ip-10-1-236-160.ap-south-1.compute.internal initContainers: - name: run-plumr image: quay.io/dosmith/fedora-vlc-iptools:gen2 command: ["/bin/sh", "-c", "/entrypoint/plumbr-entrypoint.sh > /shared-data/entrypoint.log 2>&1 &"] env: - name: TUN_IP value: "10.4.0.1" - name: UDP_LOCAL_IP value: "10.20.1.200" - name: UDP_REMOTE_IP value: "10.10.1.200" volumeMounts: - name: host-bin mountPath: /plumrbin/ - name: plumbr-entrypoint-volume mountPath: /entrypoint - name: shared-volume mountPath: /shared-data securityContext: privileged: true capabilities: add: ["NET_ADMIN","NET_RAW"] containers: - name: workload image: quay.io/dosmith/fedora-vlc-iptools:gen2 command: ["/bin/sh", "-c", "sleep 10000000000000000"] volumeMounts: - name: shared-volume mountPath: /shared-data - name: host-bin mountPath: /plumrbin/ securityContext: privileged: true volumes: - name: host-bin hostPath: path: /tmp/plumr type: Directory - name: plumbr-entrypoint-volume configMap: name: plumbr-entrypoint-config defaultMode: 0744 - name: shared-volume emptyDir: {} ``` ### Config FEC ```[root@plumbr-example-a /]# cat /shared-data/config-fec.json { "pipeline": [ { "name": "tun", "tun": { "input": "tun_input", "output": "fragmentizer_input", "ip": "10.4.0.1" } }, { "name": "fragmentizer", "fragmentizer": { "input": "fragmentizer_input", "output": "rely_encoder_input" } }, { "name": "defragmentizer", "defragmentizer": { "input": "defragmentizer_input", "output": "tun_input" } }, { "name": "rely_encoder", "relyEncoder": { "input": "rely_encoder_input", "output": "udp_input", "timeout": 100, "repair_interval": 1, "repair_target": 2, "flush_repair_timeout": 50 } }, { "name": "rely_decoder", "relyDecoder": { "input": "rely_decoder_input", "output": "defragmentizer_input", "timeout": 150, "release_in_order": true } }, { "name": "udp", "udp": { "input": "udp_input", "output": "rely_decoder_input", "remote_address": "10.20.1.200:9999" } } ], "shm_config": { "name": "plumr-client", "bytes": 1000000, "interval_ms": 100 } } ```