From 944daaf1596a50e3118120c7ddd26a525c041fb3 Mon Sep 17 00:00:00 2001 From: Alice Frosi Date: Thu, 21 May 2026 09:43:51 +0000 Subject: [PATCH] Add --host-network-populator flag to fix DNS in nested podman The image populator container runs podman pull inside a bridge network, routing DNS through aardvark-dns. In nested podman scenarios this multi-hop forwarding is unreliable, causing intermittent name resolution failures. Add a --host-network-populator flag (default off) that switches the populator to --network=host, bypassing aardvark-dns. The flag is available on both 'cluster start' and 'node add'. The nested test in hack/test-container-image.sh passes it automatically. Assisted-by: Claude Opus 4.6 (1M context) --- hack/test-container-image.sh | 8 +++++--- internal/cli/cluster/start.go | 13 ++++++++----- internal/cli/node/add.go | 13 ++++++++----- internal/cluster/cluster.go | 27 +++++++++++++++------------ internal/cluster/images.go | 8 ++++++-- internal/podman/client.go | 15 ++++++++++----- 6 files changed, 52 insertions(+), 32 deletions(-) diff --git a/hack/test-container-image.sh b/hack/test-container-image.sh index 288415a..3b95a2e 100755 --- a/hack/test-container-image.sh +++ b/hack/test-container-image.sh @@ -90,9 +90,11 @@ run_test() { "${bink_args[@]}" --help echo "--- bink cluster start ---" - local verbose_flag="" - [ "${mode}" = "nested" ] && verbose_flag="-v" - "${bink_args[@]}" cluster start --cluster-name "${cluster_name}" --api-port 0 ${verbose_flag} + local start_extra_flags="" + if [ "${mode}" = "nested" ]; then + start_extra_flags="-v --host-network-populator" + fi + "${bink_args[@]}" cluster start --cluster-name "${cluster_name}" --api-port 0 ${start_extra_flags} echo "--- bink api expose ---" "${bink_args[@]}" api expose --cluster-name "${cluster_name}" diff --git a/internal/cli/cluster/start.go b/internal/cli/cluster/start.go index e2ced5c..063148e 100644 --- a/internal/cli/cluster/start.go +++ b/internal/cli/cluster/start.go @@ -28,6 +28,7 @@ func newStartCmd() *cobra.Command { var memory int var maxMemory int var exposePath string + var hostNetworkPopulator bool cmd := &cobra.Command{ Use: "start", @@ -35,7 +36,7 @@ func newStartCmd() *cobra.Command { Long: "Create network, control plane node, and initialize Kubernetes cluster with kubeadm", RunE: func(cmd *cobra.Command, args []string) error { logger := logrus.New() - return runStart(cmd.Context(), logger, nodeName, nodeImage, apiPort, memory, maxMemory, exposePath) + return runStart(cmd.Context(), logger, nodeName, nodeImage, apiPort, memory, maxMemory, exposePath, hostNetworkPopulator) }, } @@ -45,11 +46,12 @@ func newStartCmd() *cobra.Command { cmd.Flags().IntVar(&memory, "memory", 0, "VM memory in MB (0 = use role default: 1900 for control-plane, 768 for worker)") cmd.Flags().IntVar(&maxMemory, "max-memory", 0, "VM max memory in MB for balloon (0 = use role default: 4096 for control-plane, 2048 for worker)") cmd.Flags().StringVar(&exposePath, "expose", "", "Expose API and save kubeconfig to PATH after cluster is up") + cmd.Flags().BoolVar(&hostNetworkPopulator, "host-network-populator", false, "Use host networking for the image populator container (fixes DNS in nested podman)") return cmd } -func runStart(ctx context.Context, logger *logrus.Logger, nodeName string, nodeImage string, apiPort int, memory int, maxMemory int, exposePath string) error { +func runStart(ctx context.Context, logger *logrus.Logger, nodeName string, nodeImage string, apiPort int, memory int, maxMemory int, exposePath string, hostNetworkPopulator bool) error { logger.Info("=== Creating Kubernetes cluster ===") logger.Info("") @@ -102,9 +104,10 @@ func runStart(ctx context.Context, logger *logrus.Logger, nodeName string, nodeI logger.Info("Step 5: Preparing cluster images volume...") clusterMgr := cluster.New(cluster.Config{ - Name: clusterName, - ControlPlane: nodeName, - Logger: logger, + Name: clusterName, + ControlPlane: nodeName, + HostNetworkPopulator: hostNetworkPopulator, + Logger: logger, }) clusterImagesVolume, err := clusterMgr.EnsureImagesVolume(ctx, nodeImage) diff --git a/internal/cli/node/add.go b/internal/cli/node/add.go index d43393f..af1168b 100644 --- a/internal/cli/node/add.go +++ b/internal/cli/node/add.go @@ -25,6 +25,7 @@ func newAddCmd() *cobra.Command { var role string var memory int var maxMemory int + var hostNetworkPopulator bool cmd := &cobra.Command{ Use: "add ", @@ -33,7 +34,7 @@ func newAddCmd() *cobra.Command { Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { logger := logrus.New() - return runAdd(cmd.Context(), args[0], controlPlane, nodeImage, role, memory, maxMemory, logger) + return runAdd(cmd.Context(), args[0], controlPlane, nodeImage, role, memory, maxMemory, hostNetworkPopulator, logger) }, } @@ -42,11 +43,12 @@ func newAddCmd() *cobra.Command { cmd.Flags().StringVarP(&role, "role", "r", "worker", "Node role: worker or control-plane") cmd.Flags().IntVar(&memory, "memory", 0, "VM memory in MB (0 = use role default: 1900 for control-plane, 768 for worker)") cmd.Flags().IntVar(&maxMemory, "max-memory", 0, "VM max memory in MB for balloon (0 = use role default: 4096 for control-plane, 2048 for worker)") + cmd.Flags().BoolVar(&hostNetworkPopulator, "host-network-populator", false, "Use host networking for the image populator container (fixes DNS in nested podman)") return cmd } -func runAdd(ctx context.Context, nodeName, controlPlane, nodeImage, role string, memory int, maxMemory int, logger *logrus.Logger) error { +func runAdd(ctx context.Context, nodeName, controlPlane, nodeImage, role string, memory int, maxMemory int, hostNetworkPopulator bool, logger *logrus.Logger) error { // Validate and convert role to boolean var isControlPlane bool switch role { @@ -74,9 +76,10 @@ func runAdd(ctx context.Context, nodeName, controlPlane, nodeImage, role string, // Ensure images volume exists for this node image version logger.Infof("Step 0: Ensuring cluster images volume...") clusterMgr := cluster.New(cluster.Config{ - Name: clusterName, - ControlPlane: controlPlane, - Logger: logger, + Name: clusterName, + ControlPlane: controlPlane, + HostNetworkPopulator: hostNetworkPopulator, + Logger: logger, }) clusterImagesVolume, err := clusterMgr.EnsureImagesVolume(ctx, nodeImage) diff --git a/internal/cluster/cluster.go b/internal/cluster/cluster.go index d9541d3..fa686e2 100644 --- a/internal/cluster/cluster.go +++ b/internal/cluster/cluster.go @@ -33,18 +33,20 @@ type PodmanClient interface { // Cluster represents a Kubernetes cluster type Cluster struct { - name string - controlPlane string - logger *logrus.Logger - podmanClient PodmanClient + name string + controlPlane string + hostNetworkPopulator bool + logger *logrus.Logger + podmanClient PodmanClient } // Config holds cluster configuration type Config struct { - Name string // Cluster name (default: "bink") - ControlPlane string // Control plane node name (default: "node1") - Logger *logrus.Logger - PodmanClient PodmanClient + Name string // Cluster name (default: "bink") + ControlPlane string // Control plane node name (default: "node1") + HostNetworkPopulator bool // Use host networking for the image populator container + Logger *logrus.Logger + PodmanClient PodmanClient } // New creates a new Cluster @@ -69,10 +71,11 @@ func New(cfg Config) *Cluster { } return &Cluster{ - name: cfg.Name, - controlPlane: cfg.ControlPlane, - logger: cfg.Logger, - podmanClient: client, + name: cfg.Name, + controlPlane: cfg.ControlPlane, + hostNetworkPopulator: cfg.HostNetworkPopulator, + logger: cfg.Logger, + podmanClient: client, } } diff --git a/internal/cluster/images.go b/internal/cluster/images.go index 6fc7ff0..095d49a 100644 --- a/internal/cluster/images.go +++ b/internal/cluster/images.go @@ -191,11 +191,15 @@ func (c *Cluster) populateImagesVolume(ctx context.Context, volumeName, nodeImag Destination: "/images", }}, Volumes: []*specgen.NamedVolume{{Name: volumeName, Dest: "/var/lib/containers/storage"}}, - CapAdd: []string{"SYS_ADMIN"}, - Devices: []specs.LinuxDevice{{Path: "/dev/fuse"}}, + CapAdd: []string{"SYS_ADMIN"}, + Devices: []specs.LinuxDevice{{Path: "/dev/fuse"}}, SelinuxOpts: []string{"disable"}, } + if c.hostNetworkPopulator { + opts.Network = "host" + } + _, err := c.podmanClient.ContainerCreate(ctx, opts) if err != nil { return fmt.Errorf("starting populator container (another process may be populating): %w", err) diff --git a/internal/podman/client.go b/internal/podman/client.go index 43caf67..50edc9a 100644 --- a/internal/podman/client.go +++ b/internal/podman/client.go @@ -246,11 +246,16 @@ func (c *Client) ContainerCreate(ctx context.Context, opts *ContainerCreateOptio } spec.Networks = opts.NetworkOptions } else if opts.Network != "" { - spec.NetNS = specgen.Namespace{ - NSMode: specgen.Bridge, - } - spec.Networks = map[string]nettypes.PerNetworkOptions{ - opts.Network: {}, + switch opts.Network { + case "host": + spec.NetNS = specgen.Namespace{NSMode: specgen.Host} + default: + spec.NetNS = specgen.Namespace{ + NSMode: specgen.Bridge, + } + spec.Networks = map[string]nettypes.PerNetworkOptions{ + opts.Network: {}, + } } }