Stop using kernel IP autoconfig for runtime VMs
Avoid the Alpine boot stall caused by kernel ip= autoconfig running before virtio_net is available. Split runtime and image-build boot args so managed VMs boot without kernel network autoconfig, inject a static guest network config plus bootstrap script into the runtime overlay, and keep image builds on the old path for compatibility with existing base images. Preserve executable bits when patching guest files into ext4 images and add coverage for the new boot-arg split and guest network config generation. Validated with go test ./..., a rebuilt Alpine image, and a fresh alp-fast create/ssh check that brought vm.start down to about 2.7s.
This commit is contained in:
parent
092d848620
commit
14d8563f3c
7 changed files with 186 additions and 28 deletions
|
|
@ -2,25 +2,59 @@
|
|||
set -eu
|
||||
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
CONFIG_PATH="/etc/banger-network.conf"
|
||||
|
||||
if ! command -v ip >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cmdline="$(cat /proc/cmdline 2>/dev/null || true)"
|
||||
ip_arg=""
|
||||
for arg in $cmdline; do
|
||||
case "$arg" in
|
||||
ip=*)
|
||||
ip_arg="${arg#ip=}"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
guest_ip=""
|
||||
gateway_ip=""
|
||||
netmask=""
|
||||
iface_hint=""
|
||||
dns1=""
|
||||
dns2=""
|
||||
|
||||
if [ -z "$ip_arg" ]; then
|
||||
exit 0
|
||||
fi
|
||||
load_file_config() {
|
||||
if [ ! -r "$CONFIG_PATH" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# shellcheck disable=SC1090
|
||||
. "$CONFIG_PATH"
|
||||
guest_ip="${GUEST_IP:-}"
|
||||
gateway_ip="${GATEWAY_IP:-}"
|
||||
netmask="${NETMASK:-}"
|
||||
iface_hint="${INTERFACE:-}"
|
||||
dns1="${DNS1:-}"
|
||||
dns2="${DNS2:-}"
|
||||
[ -n "$guest_ip" ]
|
||||
}
|
||||
|
||||
load_cmdline_config() {
|
||||
cmdline="$(cat /proc/cmdline 2>/dev/null || true)"
|
||||
ip_arg=""
|
||||
for arg in $cmdline; do
|
||||
case "$arg" in
|
||||
ip=*)
|
||||
ip_arg="${arg#ip=}"
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$ip_arg" ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
guest_ip="$(field 1)"
|
||||
gateway_ip="$(field 3)"
|
||||
netmask="$(field 4)"
|
||||
iface_hint="$(field 6)"
|
||||
dns1="$(field 8)"
|
||||
dns2="$(field 9)"
|
||||
[ -n "$guest_ip" ]
|
||||
}
|
||||
|
||||
field() {
|
||||
printf '%s' "$ip_arg" | cut -d: -f"$1"
|
||||
|
|
@ -81,12 +115,9 @@ find_iface() {
|
|||
return 1
|
||||
}
|
||||
|
||||
guest_ip="$(field 1)"
|
||||
gateway_ip="$(field 3)"
|
||||
netmask="$(field 4)"
|
||||
iface_hint="$(field 6)"
|
||||
dns1="$(field 8)"
|
||||
dns2="$(field 9)"
|
||||
if ! load_file_config; then
|
||||
load_cmdline_config || exit 0
|
||||
fi
|
||||
|
||||
if [ -z "$guest_ip" ]; then
|
||||
exit 0
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
package guestnet
|
||||
|
||||
import _ "embed"
|
||||
import (
|
||||
_ "embed"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
GuestScriptPath = "/usr/local/libexec/banger-network-bootstrap"
|
||||
ConfigPath = "/etc/banger-network.conf"
|
||||
SystemdServiceName = "banger-network.service"
|
||||
VoidCoreServicePath = "/etc/runit/core-services/20-banger-network.sh"
|
||||
DefaultInterface = "eth0"
|
||||
DefaultNetmask = "255.255.255.0"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -21,6 +27,18 @@ func BootstrapScript() string {
|
|||
return bootstrapScript
|
||||
}
|
||||
|
||||
func ConfigFile(guestIP, gatewayIP, dns string) []byte {
|
||||
lines := []string{
|
||||
"GUEST_IP=" + shellQuote(guestIP),
|
||||
"GATEWAY_IP=" + shellQuote(gatewayIP),
|
||||
"NETMASK=" + shellQuote(DefaultNetmask),
|
||||
"INTERFACE=" + shellQuote(DefaultInterface),
|
||||
"DNS1=" + shellQuote(dns),
|
||||
"DNS2=''",
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n") + "\n")
|
||||
}
|
||||
|
||||
func SystemdServiceUnit() string {
|
||||
return systemdService
|
||||
}
|
||||
|
|
@ -28,3 +46,7 @@ func SystemdServiceUnit() string {
|
|||
func VoidCoreService() string {
|
||||
return voidCoreService
|
||||
}
|
||||
|
||||
func shellQuote(value string) string {
|
||||
return "'" + strings.ReplaceAll(value, "'", `'"'"'`) + "'"
|
||||
}
|
||||
|
|
|
|||
24
internal/guestnet/guestnet_test.go
Normal file
24
internal/guestnet/guestnet_test.go
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package guestnet
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfigFileIncludesStaticGuestNetworking(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := string(ConfigFile("172.16.0.2", "172.16.0.1", "1.1.1.1"))
|
||||
for _, want := range []string{
|
||||
"GUEST_IP='172.16.0.2'",
|
||||
"GATEWAY_IP='172.16.0.1'",
|
||||
"NETMASK='255.255.255.0'",
|
||||
"INTERFACE='eth0'",
|
||||
"DNS1='1.1.1.1'",
|
||||
"DNS2=''",
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("ConfigFile() missing %q\nconfig:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue