penguin/monok8s

k8s image for Mono Gateway Dev Kit

commit 9225857db61aa80a1a175f0076418d1f6c8f493433f46f26cd218404e397ab45

author斟酌 鵬兄 <tgckpg@gmail.com>
date2026-04-13T19:58:23Z
subjectVPP just won't work. God help
commit 9225857db61aa80a1a175f0076418d1f6c8f493433f46f26cd218404e397ab45
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2026-04-13T19:58:23Z

    VPP just won't work. God help
---
 README.md                                     |  28 ++-
 alpine/rootfs-extra/etc/local.d/monok8s.start |   2 +-
 clitools/pkg/node/kubeadm.go                  |   2 +-
 docker/vpp.Dockerfile                         | 248 ++++++++++++++++----------
 docs/dpdk_vpp.md                              |  50 +++++-
 docs/flashing-usb.md                          |  17 +-
 makefile                                      |   3 +-
 patches/vpp/0001-buffer-fallback-log.patch    |  16 ++
 vpp/patch.sh                                  | 150 ++++++++++++++++
 9 files changed, 404 insertions(+), 112 deletions(-)

diff --git a/README.md b/README.md
index dc4fc4e..9c643a8 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
 # monok8s
 An Alpine-based Kubernetes cluster image for Mono's Gateway Development Kit
-
 https://docs.mono.si/gateway-development-kit/getting-started
 
 ## Features
@@ -16,6 +15,33 @@ https://docs.mono.si/gateway-development-kit/getting-started
 
  * USE AT YOUR OWN RISKS. I leverage ChatGPT heavily for this.
 
+## Current Status
+
+### Boostrapping
+ * [x] initramfs
+ * [x] booting into alpine
+ * [x] k8s control-plane
+ * [ ] k8s worker node
+
+### Kubernetes
+ * OSUpgrade
+   * [x] Control Plane - kubeadm upgrade apply
+   * [ ] Worker node - kubeadm upgrade node
+   * Upgrade chain
+     * [x] 1.33.3 -> 1.33.10
+     * [ ] 1.33.10 -> 1.34.1
+     * [ ] 1.34.1 -> 1.34.6
+     * [ ] 1.34.6 -> 1.35.1
+     * [ ] 1.35.1 -> 1.35.3
+ * CNI
+   * [x] default bridge-cni
+   * [ ] Cilium
+
+### Network Traffics
+ * VPP Pod
+   * [x] fmc - works? But no way to test it yet
+   * [ ] vpp - does not work
+
 ## Table of Contents
 1. Flashing
    - [USB](docs/flashing-usb.md)
diff --git a/alpine/rootfs-extra/etc/local.d/monok8s.start b/alpine/rootfs-extra/etc/local.d/monok8s.start
index f1044c1..0f39ebb 100755
--- a/alpine/rootfs-extra/etc/local.d/monok8s.start
+++ b/alpine/rootfs-extra/etc/local.d/monok8s.start
@@ -4,6 +4,6 @@ export PATH="/usr/local/bin:/usr/local/sbin:$PATH"
 
 mkdir -p /dev/hugepages
 mountpoint -q /dev/hugepages || mount -t hugetlbfs none /dev/hugepages
-echo 256 > /proc/sys/vm/nr_hugepages
+echo 640 > /proc/sys/vm/nr_hugepages
 
 /usr/local/bin/ctl init --env-file /opt/monok8s/config/cluster.env >>/var/log/monok8s/bootstrap.log 2>&1 &
diff --git a/clitools/pkg/node/kubeadm.go b/clitools/pkg/node/kubeadm.go
index 2d20806..1de4e0f 100644
--- a/clitools/pkg/node/kubeadm.go
+++ b/clitools/pkg/node/kubeadm.go
@@ -409,7 +409,7 @@ func checkImagePresent(ctx context.Context, n *NodeContext, image string) error
 
 	// crictl inspecti exits non-zero when the image is absent.
 	_, err := n.SystemRunner.RunRetry(ctx, system.RetryOptions{
-		Attempts: 3,
+		Attempts: 6,
 		Delay:    2 * system.DefaultSecond,
 	}, "crictl", "inspecti", image)
 	if err != nil {
diff --git a/docker/vpp.Dockerfile b/docker/vpp.Dockerfile
index dc980ee..989f477 100644
--- a/docker/vpp.Dockerfile
+++ b/docker/vpp.Dockerfile
@@ -84,9 +84,10 @@ RUN rm -rf build-aarch64 && \
   -Dkernel_dir=/usr/src/linux \
   -Dexamples=l2fwd,l3fwd,helloworld \
   -Dmax_lcores=4 \
-  -Dc_args="-g -Ofast -fPIC -ftls-model=local-dynamic -Wno-error=implicit-function-declaration -Wno-error=maybe-uninitialized \
-    -I/mnt/rootfs/usr/include \
-    -I/mnt/rootfs/usr/include/aarch64-linux-gnu" \
+  -Dc_args="-g3 -O0 -fPIC -fno-omit-frame-pointer \
+  -Wno-error=implicit-function-declaration -Wno-error=maybe-uninitialized \
+  -I/mnt/rootfs/usr/include \
+  -I/mnt/rootfs/usr/include/aarch64-linux-gnu" \
   -Dc_link_args="-Wl,-rpath-link=/mnt/rootfs/usr/lib/aarch64-linux-gnu \
     -L/mnt/rootfs/usr/lib/aarch64-linux-gnu \
     -L/mnt/rootfs/usr/lib \
@@ -137,19 +138,44 @@ RUN set -eux; \
 
 WORKDIR /src/vpp
 
+# BEGIN LOCAL VPP PATCHES
+COPY patches /tmp/patches
+
+RUN apt-get update && apt-get install -y --no-install-recommends patch \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN set -eux; \
+    if [ -d /tmp/patches/vpp ]; then \
+      for p in /tmp/patches/vpp/*.patch; do \
+        [ -e "$p" ] || continue; \
+        if patch -p1 --dry-run --forward < "$p" >/dev/null 2>&1; then \
+          patch -p1 --forward < "$p"; \
+        elif patch -p1 -R --dry-run < "$p" >/dev/null 2>&1; then \
+          echo "already applied: $p"; \
+        else \
+          echo "failed to apply patch: $p" >&2; \
+          exit 1; \
+        fi; \
+      done; \
+    fi
+# END LOCAL VPP PATCHES
+
+
 # Sync libs for the linker
 RUN ln -s /mnt/rootfs/usr/lib/aarch64-linux-gnu /mnt/rootfs/usr/lib/lib64 || true
 
 RUN cd build-root && \
-    export DEB_BUILD_OPTIONS="nocheck nodoc noautodbgsym" && \
+    export DEB_BUILD_OPTIONS="nocheck nodoc noautodbgsym nostrip" && \
+    export CFLAGS="-O0 -g3 -fno-omit-frame-pointer" && \
+    export CXXFLAGS="-O0 -g3 -fno-omit-frame-pointer" && \
     make V=1 \
-    PLATFORM=dpaa \
-    TAG=dpaa \
-    CROSS_SYSROOT=/mnt/rootfs \
-    CROSS_PREFIX=aarch64-linux-gnu \
-    DPDK_PATH=/mnt/rootfs/usr \
-    VPP_VERSION=${VPP_VERSION} \
-    vpp-install
+      PLATFORM=dpaa \
+      TAG=dpaa \
+      CROSS_SYSROOT=/mnt/rootfs \
+      CROSS_PREFIX=aarch64-linux-gnu \
+      DPDK_PATH=/mnt/rootfs/usr \
+      VPP_VERSION=${VPP_VERSION} \
+      vpp-install
 
 ARG FMLIB_TAR
 ARG FMC_TAR
@@ -236,98 +262,122 @@ RUN set -eux; \
     \
     # 1. Initialize policy.xml with the correct <netpcd> root tag
     echo '<?xml version="1.0" encoding="UTF-8"?>' > /tmp/fmc_staging/policy.xml; \
-    echo '<netpcd>' >> /tmp/fmc_staging/policy.xml; \
-    \
-    # 2. Extract policy names and generate dummy blocks
-    # We use a subshell and tr to handle potential weird spacing
-    POLICY_NAMES=$(grep -o 'policy="[^"]*"' /tmp/fmc_staging/config.xml | cut -d'"' -f2 | sort -u); \
-    \
-    for NAME in $POLICY_NAMES; do \
-        echo "    <policy name=\"$NAME\">" >> /tmp/fmc_staging/policy.xml; \
-        echo "        <dist_order><distributionref name=\"dist_dummy\"/></dist_order>" >> /tmp/fmc_staging/policy.xml; \
-        echo "    </policy>" >> /tmp/fmc_staging/policy.xml; \
-    done; \
-    \
-    # 3. Append the distribution and close the root tag
-    # Updated Distribution block for the Dockerfile RUN command
     cat << 'EOF' >> /tmp/fmc_staging/policy.xml
+<netpcd>
+    <policy name="hash_ipsec_src_dst_spi_policy_mac10">
+        <dist_order><distributionref name="dist_dummy"/></dist_order>
+    </policy>
+    <policy name="hash_ipsec_src_dst_spi_policy_mac9">
+        <dist_order><distributionref name="dist_dummy"/></dist_order>
+    </policy>
     <distribution name="dist_dummy">
+        <queue count="1" base="0x900"/>
         <protocols><protocolref name="ethernet"/></protocols>
     </distribution>
 </netpcd>
 EOF
 
-RUN set -eux; \
-    cd /src/vpp; \
-    mkdir -p debian; \
-    echo "10" > debian/compat; \
-    \
-    # 1. Start the Rules file
-    echo '#!/usr/bin/make -f' > debian/rules; \
-    echo '%:' >> debian/rules; \
-    printf '\tdh $@\n' >> debian/rules; \
-    \
-    echo 'override_dh_auto_configure:' >> debian/rules; \
-    printf '\t@echo "Skipping configure"\n' >> debian/rules; \
-    \
-    echo 'override_dh_auto_clean:' >> debian/rules; \
-    printf '\t@echo "Skipping clean"\n' >> debian/rules; \
-    \
-    echo 'override_dh_auto_build:' >> debian/rules; \
-    printf '\t@echo "Skipping build"\n' >> debian/rules; \
-    \
-    # 2. THE INSTALL STEP (Crucial: everything here must have a Tab)
-    echo 'override_dh_auto_install:' >> debian/rules; \
-    printf '\tmkdir -p debian/vpp/usr/bin\n' >> debian/rules; \
-    printf '\tmkdir -p debian/vpp/usr/lib\n' >> debian/rules; \
-    printf '\tmkdir -p debian/vpp/etc/fmc/config\n' >> debian/rules; \
-    \
-    # Copy VPP binaries from build-root
-    printf '\tcp -a build-root/install-dpaa-aarch64/vpp/. debian/vpp/usr/\n' >> debian/rules; \
-    \
-    # Copy FMC binary
-    printf '\tcp /src/fmc/source/fmc debian/vpp/usr/bin/\n' >> debian/rules; \
-    \
-    # PULL FROM THE STAGING AREA
-    printf '\tcp /tmp/fmc_staging/config.xml debian/vpp/etc/fmc/\n' >> debian/rules; \
-    printf '\tcp /tmp/fmc_staging/policy.xml debian/vpp/etc/fmc/\n' >> debian/rules; \
-    printf '\tcp /tmp/fmc_staging/config/* debian/vpp/etc/fmc/config/\n' >> debian/rules; \
-    \
-    # Copy system libs
-    printf '\tcp /mnt/rootfs/usr/lib/libfm-arm.so* debian/vpp/usr/lib/ 2>/dev/null || true\n' >> debian/rules; \
-    printf '\tcp /mnt/rootfs/usr/lib/libusdpaa.so* debian/vpp/usr/lib/ 2>/dev/null || true\n' >> debian/rules; \
-    \
-    echo 'override_dh_usrlocal:' >> debian/rules; \
-    printf '\t@echo "Skipping usrlocal fixup"\n' >> debian/rules; \
-    \
-    echo 'override_dh_shlibdeps:' >> debian/rules; \
-    printf '\t@echo "Skipping shlibdeps (cross-compile)"\n' >> debian/rules; \
-    \
-    chmod +x debian/rules; \
-    \
-    # 3. Control and Changelog (echo is fine here because these aren't Makefiles)
-    echo "Source: vpp" > debian/control; \
-    echo "Maintainer: NXP Builder <builder@nxp.com>" >> debian/control; \
-    echo "Section: net" >> debian/control; \
-    echo "Priority: optional" >> debian/control; \
-    echo "Build-Depends: debhelper (>= 10)" >> debian/control; \
-    echo "" >> debian/control; \
-    echo "Package: vpp" >> debian/control; \
-    echo "Architecture: arm64" >> debian/control; \
-    echo "Depends: \${shlibs:Depends}, \${misc:Depends}, libxml2, libtclap-dev" >> debian/control; \
-    echo "Description: VPP for NXP DPAA" >> debian/control; \
-    \
-    echo "vpp (${VPP_VERSION#lf-}-1) stable; urgency=low" > debian/changelog; \
-    echo "  * Custom NXP Build" >> debian/changelog; \
-    echo " -- NXP Builder <builder@nxp.com>  $(date -R)" >> debian/changelog
-
-# 5. Final Packaging
-RUN cd /src/vpp && \
-    export DEB_BUILD_OPTIONS="nocheck nodoc noautodbgsym" && \
-    export CC=aarch64-linux-gnu-gcc && \
-    export CXX=aarch64-linux-gnu-g++ && \
-    dpkg-buildpackage -us -uc -b -aarm64 -d
-
-FROM scratch
-ARG VPP_VERSION
-COPY --from=build /src/vpp_${VPP_VERSION#lf-}-1_arm64.deb /
+FROM --platform=linux/arm64 debian:trixie-slim AS runtime
+
+ARG APT_PROXY
+
+RUN if [ -n "${APT_PROXY}" ]; then \
+      echo "Acquire::http::Proxy \"http://${APT_PROXY}\";" > /mnt/rootfs/etc/apt/apt.conf.d/01proxy; \
+    fi
+
+COPY --from=build /src/vpp/build-root/install-dpaa-aarch64/vpp/ /usr/
+
+FROM --platform=linux/arm64 debian:trixie-slim AS runtime
+
+COPY --from=build /src/vpp/build-root/install-dpaa-aarch64/vpp/bin/ /usr/bin/
+COPY --from=build /src/vpp/build-root/install-dpaa-aarch64/vpp/etc/ /etc/
+COPY --from=build /src/vpp/build-root/install-dpaa-aarch64/vpp/include/ /usr/include/
+COPY --from=build /src/vpp/build-root/install-dpaa-aarch64/vpp/lib/ /usr/lib/
+COPY --from=build /src/vpp/build-root/install-dpaa-aarch64/vpp/local/ /usr/local/
+COPY --from=build /src/vpp/build-root/install-dpaa-aarch64/vpp/share/ /usr/share/
+
+# FMC bits
+COPY --from=build /src/fmc/source/fmc /usr/bin/
+COPY --from=build /tmp/fmc_staging/config.xml /etc/fmc/
+COPY --from=build /tmp/fmc_staging/policy.xml /etc/fmc/
+COPY --from=build /tmp/fmc_staging/config/ /etc/fmc/config/
+
+# NXP runtime libs
+COPY --from=build /mnt/rootfs/usr/lib/libfm-arm.so* /usr/lib/
+COPY --from=build /mnt/rootfs/usr/lib/libusdpaa.so* /usr/lib/
+
+
+# RUN set -eux; \
+#     cd /src/vpp; \
+#     mkdir -p debian; \
+#     echo "10" > debian/compat; \
+#     \
+#     # 1. Start the Rules file
+#     echo '#!/usr/bin/make -f' > debian/rules; \
+#     echo '%:' >> debian/rules; \
+#     printf '\tdh $@\n' >> debian/rules; \
+#     \
+#     echo 'override_dh_auto_configure:' >> debian/rules; \
+#     printf '\t@echo "Skipping configure"\n' >> debian/rules; \
+#     \
+#     echo 'override_dh_auto_clean:' >> debian/rules; \
+#     printf '\t@echo "Skipping clean"\n' >> debian/rules; \
+#     \
+#     echo 'override_dh_auto_build:' >> debian/rules; \
+#     printf '\t@echo "Skipping build"\n' >> debian/rules; \
+#     \
+#     # 2. THE INSTALL STEP (Crucial: everything here must have a Tab)
+#     echo 'override_dh_auto_install:' >> debian/rules; \
+#     printf '\tmkdir -p debian/vpp/usr/bin\n' >> debian/rules; \
+#     printf '\tmkdir -p debian/vpp/usr/lib\n' >> debian/rules; \
+#     printf '\tmkdir -p debian/vpp/etc/fmc/config\n' >> debian/rules; \
+#     \
+#     # Copy VPP binaries from build-root
+#     printf '\tcp -a build-root/install-dpaa-aarch64/vpp/. debian/vpp/usr/\n' >> debian/rules; \
+#     \
+#     # Copy FMC binary
+#     printf '\tcp /src/fmc/source/fmc debian/vpp/usr/bin/\n' >> debian/rules; \
+#     \
+#     # PULL FROM THE STAGING AREA
+#     printf '\tcp /tmp/fmc_staging/config.xml debian/vpp/etc/fmc/\n' >> debian/rules; \
+#     printf '\tcp /tmp/fmc_staging/policy.xml debian/vpp/etc/fmc/\n' >> debian/rules; \
+#     printf '\tcp /tmp/fmc_staging/config/* debian/vpp/etc/fmc/config/\n' >> debian/rules; \
+#     \
+#     # Copy system libs
+#     printf '\tcp /mnt/rootfs/usr/lib/libfm-arm.so* debian/vpp/usr/lib/ 2>/dev/null || true\n' >> debian/rules; \
+#     printf '\tcp /mnt/rootfs/usr/lib/libusdpaa.so* debian/vpp/usr/lib/ 2>/dev/null || true\n' >> debian/rules; \
+#     \
+#     echo 'override_dh_usrlocal:' >> debian/rules; \
+#     printf '\t@echo "Skipping usrlocal fixup"\n' >> debian/rules; \
+#     \
+#     echo 'override_dh_shlibdeps:' >> debian/rules; \
+#     printf '\t@echo "Skipping shlibdeps (cross-compile)"\n' >> debian/rules; \
+#     \
+#     chmod +x debian/rules; \
+#     \
+#     # 3. Control and Changelog (echo is fine here because these aren't Makefiles)
+#     echo "Source: vpp" > debian/control; \
+#     echo "Maintainer: NXP Builder <builder@nxp.com>" >> debian/control; \
+#     echo "Section: net" >> debian/control; \
+#     echo "Priority: optional" >> debian/control; \
+#     echo "Build-Depends: debhelper (>= 10)" >> debian/control; \
+#     echo "" >> debian/control; \
+#     echo "Package: vpp" >> debian/control; \
+#     echo "Architecture: arm64" >> debian/control; \
+#     echo "Depends: \${shlibs:Depends}, \${misc:Depends}, libxml2, libtclap-dev" >> debian/control; \
+#     echo "Description: VPP for NXP DPAA" >> debian/control; \
+#     \
+#     echo "vpp (${VPP_VERSION#lf-}-1) stable; urgency=low" > debian/changelog; \
+#     echo "  * Custom NXP Build" >> debian/changelog; \
+#     echo " -- NXP Builder <builder@nxp.com>  $(date -R)" >> debian/changelog
+# 
+# # 5. Final Packaging
+# RUN cd /src/vpp && \
+#     export DEB_BUILD_OPTIONS="nocheck nodoc nostrip noautodbgsym" && \
+#     export CC=aarch64-linux-gnu-gcc && \
+#     export CXX=aarch64-linux-gnu-g++ && \
+#     dpkg-buildpackage -us -uc -b -aarm64 -d
+# 
+# FROM scratch
+# ARG VPP_VERSION
+# COPY --from=build /src/vpp_${VPP_VERSION#lf-}-1_arm64.deb /
diff --git a/docs/dpdk_vpp.md b/docs/dpdk_vpp.md
index 6167c1d..7c141c1 100644
--- a/docs/dpdk_vpp.md
+++ b/docs/dpdk_vpp.md
@@ -88,5 +88,51 @@ Done
 Bye...
 ```
 
-## VPP
-Can't even build. The toolchain is messy. So many errors to plow through. I gave up.
+## VPP (Very Painful Process)
+
+### The fmc hurdle
+`fmc -c /etc/fmc/config.xml -p /etc/fmc/policy.xml -a -l dbg1`
+* [x] Kernel oops (fixed due to an incorrect build)
+* [ ] Finalize config.xml
+* [ ] Finalize policy.xml
+
+Logs
+```
+DBG1: Invocation of FM_Open from fmc_exec_engine_start for fm0
+DBG1: Invocation of FM_Open for fm0 succeeded
+DBG1: Invocation of FM_PCD_Open from fmc_exec_engine_start for fm0/pcd
+DBG1: Invocation of FM_PCD_Open for fm0/pcd succeeded
+DBG1: Invocation of FM_PCD_Enable from fmc_exec_engine_start for fm0/pcd
+DBG1: Invocation of FM_PCD_Enable for fm0/pcd succeeded
+DBG1: fmc_exec_engine_start - execution ended
+DBG1: Invocation of FM_PORT_Open from fmc_exec_port_start for fm0/port/MAC/9
+DBG1: Invocation of FM_PORT_Open for fm0/port/MAC/9 succeeded
+DBG1: Invocation of FM_PCD_NetEnvCharacteristicsSet from fmc_exec_port_start for fm0/port/MAC/9
+DBG1: Invocation of FM_PCD_NetEnvCharacteristicsSet for fm0/port/MAC/9 succeeded
+DBG1: Invocation of FM_PCD_KgSchemeSet from fmc_exec_scheme for fm0/port/MAC/9/dist/dist_dummy
+DBG1: Invocation of FM_PCD_KgSchemeSet for fm0/port/MAC/9/dist/dist_dummy succeeded
+DBG1: Invocation of FM_PORT_Disable from fmc_exec_port_end for fm0/port/MAC/9
+DBG1: Invocation of FM_PORT_Disable for fm0/port/MAC/9 succeeded
+DBG1: Invocation of FM_PORT_SetPCD from fmc_exec_port_end for fm0/port/MAC/9
+DBG1: Invocation of FM_PORT_SetPCD for fm0/port/MAC/9 succeeded
+DBG1: Invocation of FM_PORT_Enable from fmc_exec_port_end for fm0/port/MAC/9
+DBG1: Invocation of FM_PORT_Enable for fm0/port/MAC/9 succeeded
+DBG1: Invocation of FM_PORT_Open from fmc_exec_port_start for fm0/port/MAC/10
+DBG1: Invocation of FM_PORT_Open for fm0/port/MAC/10 succeeded
+DBG1: Invocation of FM_PCD_NetEnvCharacteristicsSet from fmc_exec_port_start for fm0/port/MAC/10
+DBG1: Invocation of FM_PCD_NetEnvCharacteristicsSet for fm0/port/MAC/10 succeeded
+DBG1: Invocation of FM_PCD_KgSchemeSet from fmc_exec_scheme for fm0/port/MAC/10/dist/dist_dummy
+DBG1: Invocation of FM_PCD_KgSchemeSet for fm0/port/MAC/10/dist/dist_dummy succeeded
+DBG1: Invocation of FM_PORT_Disable from fmc_exec_port_end for fm0/port/MAC/10
+DBG1: Invocation of FM_PORT_Disable for fm0/port/MAC/10 succeeded
+DBG1: Invocation of FM_PORT_SetPCD from fmc_exec_port_end for fm0/port/MAC/10
+DBG1: Invocation of FM_PORT_SetPCD for fm0/port/MAC/10 succeeded
+DBG1: Invocation of FM_PORT_Enable from fmc_exec_port_end for fm0/port/MAC/10
+DBG1: Invocation of FM_PORT_Enable for fm0/port/MAC/10 succeeded
+```
+
+### vpp startup blocker
+`vpp -c /etc/vpp/startup.conf`
+* [ ] Main heap alloction error
+
+
diff --git a/docs/flashing-usb.md b/docs/flashing-usb.md
index 302b8f9..de7b46c 100644
--- a/docs/flashing-usb.md
+++ b/docs/flashing-usb.md
@@ -1,24 +1,25 @@
 We have two type of flashing
-1. Flash the image directly into USB. And boot using it.
-2. Copy the files into the USB drive. Then boot into initramfs. Then flash the image into the eMMC.
+1. (Production) Copy the files into the USB drive. Then boot into initramfs. Then flash the image into the eMMC.
+2. Flash the image directly into USB. And boot using it.
 
 ## Flashing into eMMC
 1. `make release`
 2. Format the USB in vfat
 3. Copy out/[RELEASE].img.gz and out/board.itb to your usb drive's root
-4. Run
+4. Run the following commands one step at a time
 ```
 usb start
 usb tree
 fatls usb 0:1   # For fat
 ext4ls usb 0:1  # For ext4 if you insist
 fatload usb 0 0x80000000 board.itb
+extload usb 0 0x80000000 board.itb
 
 setenv bootargs "console=ttyS0,115200 earlycon=uart8250,mmio,0x21c0500 root=/dev/ram0 rootwait rw"
 bootm 0x80000000
 ```
 
-5. Inside initramfs, run
+5. Once booted into initramfs, run
 ```
 flash-emmc.sh
 ```
@@ -30,10 +31,13 @@ setenv bootargs "${bootargs_console} root=emmc:2 bootpart=A rw rootwait delayacc
 ext4load mmc 0:2 ${kernel_addr_r} /boot/kernel.itb && bootm ${kernel_addr_r};
 ```
 
-7. tail /var/log/monok8s/bootstrap.log
+7. You'll soon be booted into the console. After that, you can tail /var/log/monok8s/bootstrap.log
+   to see the bootstrapping status
 
 
-## Flashing into USB
+## Flashing into USB drive
+This will make your USB drive be the primary booting device.
+
 On MacOS
 1. ./macos/flashusb.sh
 
@@ -46,4 +50,3 @@ setenv kernel_addr_r 0xa0000000;
 setenv bootargs "${bootargs_console} root=usb:2 bootpart=A rw rootwait delayacct rootfstype=ext4";
 ext4load usb 0:2 ${kernel_addr_r} /boot/kernel.itb && bootm ${kernel_addr_r};
 ```
-
diff --git a/makefile b/makefile
index 1663071..118d279 100644
--- a/makefile
+++ b/makefile
@@ -219,7 +219,8 @@ vpp: $(VPP_TAR) $(DPDK_TAR) $(FMLIB_TAR) $(FMC_TAR) $(NXP_TAR)
 		--build-arg VPP_UPSTREAM_VERSION=$(VPP_UPSTREAM_VERSION) \
 		--build-arg DPDK_TAR=$(DPDK_TAR) \
 		--build-arg DPDK_VERSION=$(DPDK_VERSION) \
-		--output type=local,dest=./$(OUT_DIR) .
+		-t $(DOCKER_IMAGE_ROOT)/vpp-source:$(TAG) . \
+		# --output type=local,dest=./$(OUT_DIR) .
 	docker buildx build --platform linux/arm64 \
 		-f docker/vpp-container.Dockerfile \
 		--build-arg APT_PROXY=$(APT_PROXY) \
diff --git a/patches/vpp/0001-buffer-fallback-log.patch b/patches/vpp/0001-buffer-fallback-log.patch
new file mode 100644
index 0000000..6dba720
--- /dev/null
+++ b/patches/vpp/0001-buffer-fallback-log.patch
@@ -0,0 +1,16 @@
+--- a/src/vlib/buffer.c
++++ b/src/vlib/buffer.c
+@@ -710,10 +710,10 @@
+       if (!error)
+ 	goto buffer_pool_create;
+
+       /* If alloc failed, retry without hugepages */
+-      vlib_log_warn (bm->log_default,
+-		     "numa[%u] falling back to non-hugepage backed "
+-		     "buffer pool (%U)", numa_node, format_clib_error, error);
++      fformat (stderr,
++	       "numa[%u] falling back to non-hugepage backed buffer pool\n",
++	       numa_node);
+       clib_error_free (error);
+
+       error = vlib_physmem_shared_map_create
diff --git a/vpp/patch.sh b/vpp/patch.sh
new file mode 100755
index 0000000..43273c2
--- /dev/null
+++ b/vpp/patch.sh
@@ -0,0 +1,150 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+DOCKERFILE="${1:-Dockerfile}"
+SOURCE_DIR="${2:-}"
+
+PATCH_DIR="patches/vpp"
+PATCH_FILE="${PATCH_DIR}/0001-buffer-fallback-log.patch"
+
+mkdir -p "${PATCH_DIR}"
+
+cat > "${PATCH_FILE}" <<'PATCH'
+--- a/src/vlib/buffer.c
++++ b/src/vlib/buffer.c
+@@ -710,10 +710,10 @@
+       if (!error)
+ 	goto buffer_pool_create;
+
+       /* If alloc failed, retry without hugepages */
+-      vlib_log_warn (bm->log_default,
+-		     "numa[%u] falling back to non-hugepage backed "
+-		     "buffer pool (%U)", numa_node, format_clib_error, error);
++      fformat (stderr,
++	       "numa[%u] falling back to non-hugepage backed buffer pool\n",
++	       numa_node);
+       clib_error_free (error);
+
+       error = vlib_physmem_shared_map_create
+PATCH
+
+apply_one_patch() {
+  local src_dir="$1"
+  local patch_file="$2"
+
+  if patch -d "$src_dir" -p1 --dry-run --forward < "$patch_file" >/dev/null 2>&1; then
+    echo "Applying $(basename "$patch_file") to $src_dir"
+    patch -d "$src_dir" -p1 --forward < "$patch_file"
+    return 0
+  fi
+
+  if patch -d "$src_dir" -p1 -R --dry-run < "$patch_file" >/dev/null 2>&1; then
+    echo "Already applied: $(basename "$patch_file")"
+    return 0
+  fi
+
+  echo "ERROR: could not apply $(basename "$patch_file") cleanly in $src_dir" >&2
+  return 1
+}
+
+apply_all_local() {
+  local src_dir="$1"
+
+  [ -d "$src_dir" ] || {
+    echo "Local source dir does not exist: $src_dir" >&2
+    return 1
+  }
+
+  local p
+  for p in "${PATCH_DIR}"/*.patch; do
+    [ -e "$p" ] || continue
+    apply_one_patch "$src_dir" "$p"
+  done
+}
+
+patch_dockerfile() {
+  local dockerfile="$1"
+
+  [ -f "$dockerfile" ] || {
+    echo "Dockerfile skipped" >&2
+    return 0
+  }
+
+  python3 - "$dockerfile" <<'PY'
+import sys
+from pathlib import Path
+
+dockerfile = Path(sys.argv[1])
+text = dockerfile.read_text()
+
+begin = "# BEGIN LOCAL VPP PATCHES"
+end = "# END LOCAL VPP PATCHES"
+
+block = r'''# BEGIN LOCAL VPP PATCHES
+COPY patches /tmp/patches
+
+RUN apt-get update && apt-get install -y --no-install-recommends patch \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN set -eux; \
+    if [ -d /tmp/patches/vpp ]; then \
+      for p in /tmp/patches/vpp/*.patch; do \
+        [ -e "$p" ] || continue; \
+        if patch -p1 --dry-run --forward < "$p" >/dev/null 2>&1; then \
+          patch -p1 --forward < "$p"; \
+        elif patch -p1 -R --dry-run < "$p" >/dev/null 2>&1; then \
+          echo "already applied: $p"; \
+        else \
+          echo "failed to apply patch: $p" >&2; \
+          exit 1; \
+        fi; \
+      done; \
+    fi
+# END LOCAL VPP PATCHES
+'''
+
+# Remove an older copy of the block if it exists.
+while begin in text and end in text:
+    s = text.index(begin)
+    e = text.index(end, s) + len(end)
+    while e < len(text) and text[e] in "\r\n":
+        e += 1
+    text = text[:s] + text[e:]
+
+needle = "WORKDIR /src/vpp"
+if needle not in text:
+    raise SystemExit(f'Could not find insertion point: {needle}')
+
+text = text.replace(needle, needle + "\n\n" + block, 1)
+dockerfile.write_text(text)
+PY
+
+  echo "Patched Dockerfile: $dockerfile"
+}
+
+patch_dockerfile "$DOCKERFILE"
+
+if [ -n "${SOURCE_DIR}" ]; then
+  apply_all_local "$SOURCE_DIR"
+else
+  echo "No local source dir given; only Dockerfile + patch files updated."
+fi
+
+cat <<EOF
+
+Done.
+
+Created:
+  ${PATCH_FILE}
+
+Updated:
+  ${DOCKERFILE}
+
+Usage:
+  $(basename "$0") Dockerfile /path/to/extracted/vpp
+
+Examples:
+  $(basename "$0") Dockerfile /src/vpp
+  $(basename "$0") path/to/Dockerfile ./vpp
+
+EOF