diff --git a/internal/app/machined/pkg/controllers/cluster/local_affiliate_test.go b/internal/app/machined/pkg/controllers/cluster/local_affiliate_test.go index 3f965f98fe..d538573472 100644 --- a/internal/app/machined/pkg/controllers/cluster/local_affiliate_test.go +++ b/internal/app/machined/pkg/controllers/cluster/local_affiliate_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/siderolabs/gen/xslices" + "github.com/siderolabs/go-pointer" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -169,7 +170,7 @@ func (suite *LocalAffiliateSuite) TestCPGeneration() { asrt.Equal("Talos ("+version.Tag+")", spec.OperatingSystem) asrt.Equal(cluster.KubeSpanAffiliateSpec{}, spec.KubeSpan) asrt.NotNil(spec.ControlPlane) - asrt.Equal(6445, spec.ControlPlane.APIServerPort) + asrt.Equal(6445, pointer.SafeDeref(spec.ControlPlane).APIServerPort) }) discoveryConfig.TypedSpec().DiscoveryEnabled = false diff --git a/internal/app/machined/pkg/controllers/config/acquire.go b/internal/app/machined/pkg/controllers/config/acquire.go index 1a2119119d..242ba29e28 100644 --- a/internal/app/machined/pkg/controllers/config/acquire.go +++ b/internal/app/machined/pkg/controllers/config/acquire.go @@ -214,6 +214,12 @@ func (validationModeDiskConfig) RequiresInstall() bool { return false } +// InContainer implements validation.RuntimeMode interface. +func (validationModeDiskConfig) InContainer() bool { + // containers don't persist config to disk + return false +} + // String implements validation.RuntimeMode interface. func (validationModeDiskConfig) String() string { return "diskConfig" diff --git a/internal/app/machined/pkg/controllers/config/acquire_test.go b/internal/app/machined/pkg/controllers/config/acquire_test.go index 1f616a014d..140afc34d0 100644 --- a/internal/app/machined/pkg/controllers/config/acquire_test.go +++ b/internal/app/machined/pkg/controllers/config/acquire_test.go @@ -128,6 +128,10 @@ func (v validationModeMock) RequiresInstall() bool { return false } +func (v validationModeMock) InContainer() bool { + return false +} + func TestAcquireSuite(t *testing.T) { t.Parallel() diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go index fb1c027d7e..5dffccdb72 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache.go @@ -127,10 +127,6 @@ func (ctrl *DNSResolveCacheController) Run(ctx context.Context, r controller.Run return fmt.Errorf("error creating dns runner: %w", rErr) } - if runner == nil { - continue - } - ctrl.runners[runnerCfg] = pair.MakePair(runner.Start(ctrl.handleDone(ctx, logger))) } @@ -264,14 +260,7 @@ func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dns. case "udp", "udp6": packetConn, err := dns.NewUDPPacketConn(cfg.net, cfg.addr.String()) if err != nil { - if cfg.net == "udp6" { - logger.Warn("error creating UDPv6 listener", zap.Error(err)) - - // If we can't bind to ipv6, we can continue with ipv4 - return nil, nil - } - - return nil, fmt.Errorf("error creating udp packet conn: %w", err) + return nil, fmt.Errorf("error creating %q packet conn: %w", cfg.net, err) } serverOpts = dns.ServerOptions{ @@ -283,14 +272,7 @@ func newDNSRunner(cfg runnerConfig, cache *dns.Cache, logger *zap.Logger) (*dns. case "tcp", "tcp6": listener, err := dns.NewTCPListener(cfg.net, cfg.addr.String()) if err != nil { - if cfg.net == "tcp6" { - logger.Warn("error creating TCPv6 listener", zap.Error(err)) - - // If we can't bind to ipv6, we can continue with ipv4 - return nil, nil - } - - return nil, fmt.Errorf("error creating tcp listener: %w", err) + return nil, fmt.Errorf("error creating %q listener: %w", cfg.net, err) } serverOpts = dns.ServerOptions{ diff --git a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go index 84d6cad4ad..ac3f65b8a2 100644 --- a/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go +++ b/internal/app/machined/pkg/controllers/network/dns_resolve_cache_test.go @@ -176,6 +176,5 @@ func getDynamicPort() (string, error) { func makeAddrs(port string) []netip.AddrPort { return []netip.AddrPort{ netip.MustParseAddrPort("127.0.0.53:" + port), - netip.MustParseAddrPort("[::1]:" + port), } } diff --git a/internal/app/machined/pkg/controllers/network/etcfile.go b/internal/app/machined/pkg/controllers/network/etcfile.go index c9352fc8a6..80061ae7e5 100644 --- a/internal/app/machined/pkg/controllers/network/etcfile.go +++ b/internal/app/machined/pkg/controllers/network/etcfile.go @@ -22,6 +22,7 @@ import ( "go.uber.org/zap" efiles "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/files" + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" talosconfig "github.com/siderolabs/talos/pkg/machinery/config" "github.com/siderolabs/talos/pkg/machinery/resources/config" "github.com/siderolabs/talos/pkg/machinery/resources/files" @@ -31,6 +32,7 @@ import ( // EtcFileController creates /etc/hostname and /etc/resolv.conf files based on finalized network configuration. type EtcFileController struct { PodResolvConfPath string + V1Alpha1Mode runtime.Mode } // Name implements controller.Controller interface. @@ -139,7 +141,8 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, lo hostnameStatusSpec = hostnameStatus.TypedSpec() } - if resolverStatus != nil && hostDNSCfg != nil { + if resolverStatus != nil && hostDNSCfg != nil && !ctrl.V1Alpha1Mode.InContainer() { + // in container mode, keep the original resolv.conf to use the resolvers supplied by the container runtime if err = safe.WriterModify(ctx, r, files.NewEtcFileSpec(files.NamespaceName, "resolv.conf"), func(r *files.EtcFileSpec) error { r.TypedSpec().Contents = renderResolvConf(pickNameservers(hostDNSCfg, resolverStatus), hostnameStatusSpec, cfgProvider) @@ -186,7 +189,7 @@ func (ctrl *EtcFileController) Run(ctx context.Context, r controller.Runtime, lo } } -var localDNS = []netip.Addr{netip.MustParseAddr("127.0.0.53"), netip.MustParseAddr("::1")} +var localDNS = []netip.Addr{netip.MustParseAddr("127.0.0.53")} func pickNameservers(hostDNSCfg *network.HostDNSConfig, resolverStatus *network.ResolverStatus) []netip.Addr { if hostDNSCfg.TypedSpec().Enabled { diff --git a/internal/app/machined/pkg/controllers/network/etcfile_test.go b/internal/app/machined/pkg/controllers/network/etcfile_test.go index 0ff64b8786..b481269538 100644 --- a/internal/app/machined/pkg/controllers/network/etcfile_test.go +++ b/internal/app/machined/pkg/controllers/network/etcfile_test.go @@ -27,6 +27,7 @@ import ( "github.com/stretchr/testify/suite" netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network" + v1alpha1runtime "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" "github.com/siderolabs/talos/pkg/logging" "github.com/siderolabs/talos/pkg/machinery/config/container" "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1" @@ -73,6 +74,7 @@ func (suite *EtcFileConfigSuite) SetupTest() { suite.Require().NoError(suite.runtime.RegisterController(&netctrl.EtcFileController{ PodResolvConfPath: suite.podResolvConfPath, + V1Alpha1Mode: v1alpha1runtime.ModeMetal, })) u, err := url.Parse("https://foo:6443") @@ -126,7 +128,6 @@ func (suite *EtcFileConfigSuite) SetupTest() { suite.hostDNSConfig.TypedSpec().Enabled = true suite.hostDNSConfig.TypedSpec().ListenAddresses = []netip.AddrPort{ netip.MustParseAddrPort("127.0.0.53:53"), - netip.MustParseAddrPort("[::1]:53"), netip.MustParseAddrPort("10.96.0.9:53"), } suite.hostDNSConfig.TypedSpec().ServiceHostDNSAddress = netip.MustParseAddr("10.96.0.9") @@ -207,6 +208,8 @@ func (suite *EtcFileConfigSuite) testFiles(resources []resource.Resource, conten return retry.ExpectedErrorf("missing pod %s", suite.podResolvConfPath) case err != nil: return err + case len(file) == 0: + return retry.ExpectedErrorf("empty pod %s", suite.podResolvConfPath) default: suite.Assert().Equal(contents.resolvGlobalConf, string(file)) @@ -225,7 +228,7 @@ func (suite *EtcFileConfigSuite) TestComplete() { []resource.Resource{suite.cfg, suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.hostDNSConfig}, etcFileContents{ hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n10.0.0.1 a b\n10.0.0.2 c d\n", //nolint:lll - resolvConf: "nameserver 127.0.0.53\nnameserver ::1\n\nsearch example.com\n", + resolvConf: "nameserver 127.0.0.53\n\nsearch example.com\n", resolvGlobalConf: "nameserver 10.96.0.9\n\nsearch example.com\n", }, ) @@ -236,7 +239,7 @@ func (suite *EtcFileConfigSuite) TestNoExtraHosts() { []resource.Resource{suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.hostDNSConfig}, etcFileContents{ hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n", - resolvConf: "nameserver 127.0.0.53\nnameserver ::1\n\nsearch example.com\n", + resolvConf: "nameserver 127.0.0.53\n\nsearch example.com\n", resolvGlobalConf: "nameserver 10.96.0.9\n\nsearch example.com\n", }, ) @@ -259,7 +262,7 @@ func (suite *EtcFileConfigSuite) TestNoSearchDomain() { []resource.Resource{cfg, suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.hostDNSConfig}, etcFileContents{ hosts: "127.0.0.1 localhost\n33.11.22.44 foo.example.com foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n", - resolvConf: "nameserver 127.0.0.53\nnameserver ::1\n", + resolvConf: "nameserver 127.0.0.53\n", resolvGlobalConf: "nameserver 10.96.0.9\n", }, ) @@ -272,7 +275,7 @@ func (suite *EtcFileConfigSuite) TestNoDomainname() { []resource.Resource{suite.defaultAddress, suite.hostnameStatus, suite.resolverStatus, suite.hostDNSConfig}, etcFileContents{ hosts: "127.0.0.1 localhost\n33.11.22.44 foo\n::1 localhost ip6-localhost ip6-loopback\nff02::1 ip6-allnodes\nff02::2 ip6-allrouters\n", - resolvConf: "nameserver 127.0.0.53\nnameserver ::1\n", + resolvConf: "nameserver 127.0.0.53\n", resolvGlobalConf: "nameserver 10.96.0.9\n", }, ) @@ -283,7 +286,7 @@ func (suite *EtcFileConfigSuite) TestOnlyResolvers() { []resource.Resource{suite.resolverStatus, suite.hostDNSConfig}, etcFileContents{ hosts: "", - resolvConf: "nameserver 127.0.0.53\nnameserver ::1\n", + resolvConf: "nameserver 127.0.0.53\n", resolvGlobalConf: "nameserver 10.96.0.9\n", }, ) diff --git a/internal/app/machined/pkg/controllers/network/hostdns_config.go b/internal/app/machined/pkg/controllers/network/hostdns_config.go index 11e0f71d27..4f437d32d1 100644 --- a/internal/app/machined/pkg/controllers/network/hostdns_config.go +++ b/internal/app/machined/pkg/controllers/network/hostdns_config.go @@ -87,7 +87,6 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti if err := safe.WriterModify(ctx, r, network.NewHostDNSConfig(network.HostDNSConfigID), func(res *network.HostDNSConfig) error { res.TypedSpec().ListenAddresses = []netip.AddrPort{ netip.MustParseAddrPort("127.0.0.53:53"), - netip.MustParseAddrPort("[::1]:53"), } res.TypedSpec().ServiceHostDNSAddress = netip.Addr{} diff --git a/internal/app/machined/pkg/controllers/network/status.go b/internal/app/machined/pkg/controllers/network/status.go index 30c85b2923..b98978272b 100644 --- a/internal/app/machined/pkg/controllers/network/status.go +++ b/internal/app/machined/pkg/controllers/network/status.go @@ -16,12 +16,15 @@ import ( "github.com/siderolabs/gen/value" "go.uber.org/zap" + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" "github.com/siderolabs/talos/pkg/machinery/resources/files" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) -// StatusController manages secrets.Etcd based on configuration. -type StatusController struct{} +// StatusController manages network.Status based on state of other resources. +type StatusController struct { + V1Alpha1Mode runtime.Mode +} // Name implements controller.Controller interface. func (ctrl *StatusController) Name() string { @@ -143,6 +146,11 @@ func (ctrl *StatusController) Run(ctx context.Context, r controller.Runtime, log result.EtcFilesReady = true for _, requiredFile := range []string{"hosts", "resolv.conf"} { + // in container mode, ignore resolv.conf, it's managed by the container runtime + if ctrl.V1Alpha1Mode.InContainer() && requiredFile == "resolv.conf" { + continue + } + _, err = r.Get(ctx, resource.NewMetadata(files.NamespaceName, files.EtcFileStatusType, requiredFile, resource.VersionUndefined)) if err != nil { if !state.IsNotFoundError(err) { diff --git a/internal/app/machined/pkg/controllers/network/status_test.go b/internal/app/machined/pkg/controllers/network/status_test.go index 7ff3f649d6..0d7ac23bec 100644 --- a/internal/app/machined/pkg/controllers/network/status_test.go +++ b/internal/app/machined/pkg/controllers/network/status_test.go @@ -16,6 +16,7 @@ import ( "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/ctest" netctrl "github.com/siderolabs/talos/internal/app/machined/pkg/controllers/network" + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" "github.com/siderolabs/talos/pkg/machinery/resources/files" "github.com/siderolabs/talos/pkg/machinery/resources/network" ) @@ -101,7 +102,11 @@ func TestStatusSuite(t *testing.T) { DefaultSuite: ctest.DefaultSuite{ Timeout: 3 * time.Second, AfterSetup: func(suite *ctest.DefaultSuite) { - suite.Require().NoError(suite.Runtime().RegisterController(&netctrl.StatusController{})) + suite.Require().NoError(suite.Runtime().RegisterController( + &netctrl.StatusController{ + V1Alpha1Mode: runtime.ModeMetal, + }, + )) }, }, }) diff --git a/internal/app/machined/pkg/runtime/mode.go b/internal/app/machined/pkg/runtime/mode.go index 88186d59cd..ec6ca69e55 100644 --- a/internal/app/machined/pkg/runtime/mode.go +++ b/internal/app/machined/pkg/runtime/mode.go @@ -52,6 +52,11 @@ func (m Mode) RequiresInstall() bool { return m == ModeMetal } +// InContainer implements config.RuntimeMode. +func (m Mode) InContainer() bool { + return m == ModeContainer +} + // Supports returns mode capability. func (m Mode) Supports(feature ModeCapability) bool { return (m.capabilities() & uint64(feature)) != 0 diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/container.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/container.go index a07fab96e5..ab89daa1a7 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/container.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/container.go @@ -6,7 +6,6 @@ package container import ( - "bytes" "context" "encoding/base64" "log" @@ -16,8 +15,8 @@ import ( "github.com/siderolabs/go-procfs/procfs" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime" + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files" "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors" - "github.com/siderolabs/talos/pkg/machinery/resources/network" runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime" ) @@ -60,26 +59,25 @@ func (c *Container) KernelArgs(string) procfs.Parameters { func (c *Container) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error { networkConfig := &runtime.PlatformNetworkConfig{} - hostname, err := os.ReadFile("/etc/hostname") + hostnameSpec, err := files.ReadHostname("/etc/hostname") if err != nil { return err } - hostname = bytes.TrimSpace(hostname) - - hostnameSpec := network.HostnameSpecSpec{ - ConfigLayer: network.ConfigPlatform, - } + networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec) - if err := hostnameSpec.ParseFQDN(string(hostname)); err != nil { + resolverSpec, err := files.ReadResolvConf("/etc/resolv.conf") + if err != nil { return err } - networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec) + if len(resolverSpec.DNSServers) > 0 { + networkConfig.Resolvers = append(networkConfig.Resolvers, resolverSpec) + } networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{ Platform: c.Name(), - Hostname: string(hostname), + Hostname: hostnameSpec.FQDN(), InstanceType: os.Getenv("TALOSSKU"), } diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/hostname.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/hostname.go new file mode 100644 index 0000000000..955c2fbf1a --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/hostname.go @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package files provides internal methods to container platform to read files. +package files + +import ( + "bytes" + "os" + + "github.com/siderolabs/talos/pkg/machinery/resources/network" +) + +// ReadHostname reads and parses /etc/hostname file. +func ReadHostname(path string) (network.HostnameSpecSpec, error) { + hostname, err := os.ReadFile(path) + if err != nil { + return network.HostnameSpecSpec{}, err + } + + hostname = bytes.TrimSpace(hostname) + + hostnameSpec := network.HostnameSpecSpec{ + ConfigLayer: network.ConfigPlatform, + } + + if err = hostnameSpec.ParseFQDN(string(hostname)); err != nil { + return network.HostnameSpecSpec{}, err + } + + return hostnameSpec, nil +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/hostname_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/hostname_test.go new file mode 100644 index 0000000000..a21f000050 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/hostname_test.go @@ -0,0 +1,23 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package files_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files" +) + +func TestReadHostname(t *testing.T) { + t.Parallel() + + spec, err := files.ReadHostname("testdata/hostname") + require.NoError(t, err) + + require.Equal(t, "foo", spec.Hostname) + require.Equal(t, "example.com", spec.Domainname) +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/resolv.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/resolv.go new file mode 100644 index 0000000000..9995b3710c --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/resolv.go @@ -0,0 +1,42 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package files + +import ( + "bytes" + "net/netip" + "os" + + "github.com/siderolabs/talos/pkg/machinery/resources/network" +) + +// ReadResolvConf reads and parses /etc/resolv.conf file. +func ReadResolvConf(path string) (network.ResolverSpecSpec, error) { + resolverSpec := network.ResolverSpecSpec{ + ConfigLayer: network.ConfigPlatform, + } + + resolvers, err := os.ReadFile(path) + if err != nil { + return resolverSpec, err + } + + for _, line := range bytes.Split(resolvers, []byte("\n")) { + line = bytes.TrimSpace(line) + line, _, _ = bytes.Cut(line, []byte("#")) + + if !bytes.HasPrefix(line, []byte("nameserver")) { + continue + } + + line = bytes.TrimSpace(bytes.TrimPrefix(line, []byte("nameserver"))) + + if addr, err := netip.ParseAddr(string(line)); err == nil { + resolverSpec.DNSServers = append(resolverSpec.DNSServers, addr) + } + } + + return resolverSpec, nil +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/resolv_test.go b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/resolv_test.go new file mode 100644 index 0000000000..7d7053d1d7 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/resolv_test.go @@ -0,0 +1,26 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package files_test + +import ( + "net/netip" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files" +) + +func TestReadResolvConf(t *testing.T) { + t.Parallel() + + spec, err := files.ReadResolvConf("testdata/resolv.conf") + require.NoError(t, err) + + require.Equal(t, []netip.Addr{ + netip.MustParseAddr("127.0.0.53"), + netip.MustParseAddr("::1"), + }, spec.DNSServers) +} diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/testdata/hostname b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/testdata/hostname new file mode 100644 index 0000000000..7488fabe76 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/testdata/hostname @@ -0,0 +1 @@ +foo.example.com diff --git a/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/testdata/resolv.conf b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/testdata/resolv.conf new file mode 100644 index 0000000000..cf5777b6d4 --- /dev/null +++ b/internal/app/machined/pkg/runtime/v1alpha1/platform/container/internal/files/testdata/resolv.conf @@ -0,0 +1,8 @@ +# This is /run/systemd/resolve/stub-resolv.conf managed by man:systemd-resolved(8). +# Do not edit. + +nameserver 127.0.0.53 # v4 one +options edns0 trust-ad +search . + + nameserver ::1 # this is V6 diff --git a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go index 52268e9fc7..b7d0a29149 100644 --- a/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go +++ b/internal/app/machined/pkg/runtime/v1alpha2/v1alpha2_controller.go @@ -199,6 +199,7 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error &network.DNSUpstreamController{}, &network.EtcFileController{ PodResolvConfPath: constants.PodResolvConfPath, + V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(), }, &network.HardwareAddrController{}, &network.HostDNSConfigController{}, @@ -245,7 +246,9 @@ func (ctrl *Controller) Run(ctx context.Context, drainer *runtime.Drainer) error &network.RouteMergeController{}, &network.RouteSpecController{}, &network.RouteStatusController{}, - &network.StatusController{}, + &network.StatusController{ + V1Alpha1Mode: ctrl.v1alpha1Runtime.State().Platform().Mode(), + }, &network.TimeServerConfigController{ Cmdline: procfs.ProcCmdline(), }, diff --git a/internal/integration/api/common.go b/internal/integration/api/common.go index 0e32e88578..bc2932dfca 100644 --- a/internal/integration/api/common.go +++ b/internal/integration/api/common.go @@ -8,7 +8,6 @@ package api import ( "context" - "os" "strings" "time" @@ -46,15 +45,15 @@ func (suite *CommonSuite) TearDownTest() { // TestVirtioModulesLoaded verifies that the virtio modules are loaded. func (suite *CommonSuite) TestVirtioModulesLoaded() { - if provisioner := os.Getenv("PROVISIONER"); provisioner != "qemu" { + if suite.Cluster == nil || suite.Cluster.Provisioner() != "qemu" { suite.T().Skip("skipping virtio test since provisioner is not qemu") } expectedVirtIOModules := map[string]string{ - "virtio_balloon": "", - "virtio_pci": "", - "virtio_pci_legacy_dev": "", - "virtio_pci_modern_dev": "", + "virtio_balloon": "virtio_balloon.ko", + "virtio_pci": "virtio_pci.ko", + "virtio_pci_legacy_dev": "virtio_pci_legacy_dev.ko", + "virtio_pci_modern_dev": "virtio_pci_modern_dev.ko", } node := suite.RandomDiscoveredNodeInternalIP() @@ -63,7 +62,7 @@ func (suite *CommonSuite) TestVirtioModulesLoaded() { // TestCommonDefaults verifies that the default ulimits are set. func (suite *CommonSuite) TestCommonDefaults() { - if provisioner := os.Getenv("PROVISIONER"); provisioner == "docker" { + if suite.Cluster != nil && suite.Cluster.Provisioner() == "docker" { suite.T().Skip("skipping ulimits test since provisioner is docker") } @@ -83,14 +82,19 @@ virtual memory (kb) (-v) unlimited file locks (-x) unlimited ` - _, err := suite.Clientset.CoreV1().Pods("default").Create(suite.ctx, &corev1.Pod{ + const ( + namespace = "default" + pod = "defaults-test" + ) + + _, err := suite.Clientset.CoreV1().Pods(namespace).Create(suite.ctx, &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: "defaults-test", + Name: pod, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{ { - Name: "defaults-test", + Name: pod, Image: "alpine", Command: []string{ "tail", @@ -101,20 +105,61 @@ file locks (-x) unlimited }, }, }, metav1.CreateOptions{}) - defer suite.Clientset.CoreV1().Pods("default").Delete(suite.ctx, "defaults-test", metav1.DeleteOptions{}) //nolint:errcheck suite.Require().NoError(err) + defer suite.Clientset.CoreV1().Pods(namespace).Delete(suite.ctx, pod, metav1.DeleteOptions{}) //nolint:errcheck + // wait for the pod to be ready - suite.Require().NoError(suite.WaitForPodToBeRunning(suite.ctx, 10*time.Minute, "default", "defaults-test")) + suite.Require().NoError(suite.WaitForPodToBeRunning(suite.ctx, 10*time.Minute, namespace, pod)) - stdout, stderr, err := suite.ExecuteCommandInPod(suite.ctx, "default", "defaults-test", "ulimit -c -d -e -f -l -m -n -q -r -s -t -v -x") + stdout, stderr, err := suite.ExecuteCommandInPod(suite.ctx, namespace, pod, "ulimit -c -d -e -f -l -m -n -q -r -s -t -v -x") suite.Require().NoError(err) suite.Require().Equal("", stderr) suite.Require().Equal(strings.TrimPrefix(expectedUlimit, "\n"), stdout) } +// TestDNSResolver verifies that external DNS resolving works from a pod. +func (suite *CommonSuite) TestDNSResolver() { + const ( + namespace = "default" + pod = "dns-test" + ) + + _, err := suite.Clientset.CoreV1().Pods(namespace).Create(suite.ctx, &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pod, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: pod, + Image: "alpine", + Command: []string{ + "tail", + "-f", + "/dev/null", + }, + }, + }, + }, + }, metav1.CreateOptions{}) + + suite.Require().NoError(err) + + defer suite.Clientset.CoreV1().Pods(namespace).Delete(suite.ctx, pod, metav1.DeleteOptions{}) //nolint:errcheck + + // wait for the pod to be ready + suite.Require().NoError(suite.WaitForPodToBeRunning(suite.ctx, 10*time.Minute, namespace, pod)) + + stdout, stderr, err := suite.ExecuteCommandInPod(suite.ctx, namespace, pod, "wget https://www.google.com/") + suite.Require().NoError(err) + + suite.Require().Equal("", stdout) + suite.Require().Contains(stderr, "'index.html' saved") +} + func init() { allSuites = append(allSuites, &CommonSuite{}) } diff --git a/internal/integration/cli/validate.go b/internal/integration/cli/validate.go index 77ed19a9c4..960485fcfd 100644 --- a/internal/integration/cli/validate.go +++ b/internal/integration/cli/validate.go @@ -52,7 +52,7 @@ func (suite *ValidateSuite) TestValidate() { ) for _, configFile := range []string{"controlplane.yaml", "worker.yaml"} { - for _, mode := range []string{"cloud", "container"} { + for _, mode := range []string{"cloud", "metal"} { suite.Run(fmt.Sprintf("%s-%s", configFile, mode), func() { suite.RunCLI([]string{"validate", "-m", mode, "-c", configFile, "--strict"}) }) diff --git a/internal/integration/k8s/tink.go b/internal/integration/k8s/tink.go index 57f4f1f950..1c0aa1e5e8 100644 --- a/internal/integration/k8s/tink.go +++ b/internal/integration/k8s/tink.go @@ -143,6 +143,7 @@ func (suite *TinkSuite) TestDeploy() { fmt.Sprintf("https://%s", net.JoinHostPort(lbNode, strconv.Itoa(k8sPort))), constants.DefaultKubernetesVersion, generate.WithAdditionalSubjectAltNames([]string{lbNode}), + generate.WithHostDNSForwardKubeDNSToHost(true), ) suite.Require().NoError(err) diff --git a/pkg/machinery/config/container/container_test.go b/pkg/machinery/config/container/container_test.go index 7c66fba10a..e34dd16e22 100644 --- a/pkg/machinery/config/container/container_test.go +++ b/pkg/machinery/config/container/container_test.go @@ -208,3 +208,7 @@ func (validationMode) String() string { func (validationMode) RequiresInstall() bool { return false } + +func (validationMode) InContainer() bool { + return false +} diff --git a/pkg/machinery/config/contract.go b/pkg/machinery/config/contract.go index 104a6b637a..6bf7545bb7 100644 --- a/pkg/machinery/config/contract.go +++ b/pkg/machinery/config/contract.go @@ -181,8 +181,8 @@ func (contract *VersionContract) KubePrismEnabled() bool { return contract.Greater(TalosVersion1_5) } -// LocalDNSEnabled returns true if local dns router should be enabled by default. -func (contract *VersionContract) LocalDNSEnabled() bool { +// HostDNSEnabled returns true if host dns router should be enabled by default. +func (contract *VersionContract) HostDNSEnabled() bool { return contract.Greater(TalosVersion1_6) } diff --git a/pkg/machinery/config/contract_test.go b/pkg/machinery/config/contract_test.go index 40af795595..c3d82acb5e 100644 --- a/pkg/machinery/config/contract_test.go +++ b/pkg/machinery/config/contract_test.go @@ -66,7 +66,7 @@ func TestContractCurrent(t *testing.T) { assert.True(t, contract.SecretboxEncryptionSupported()) assert.True(t, contract.DiskQuotaSupportEnabled()) assert.True(t, contract.KubePrismEnabled()) - assert.True(t, contract.LocalDNSEnabled()) + assert.True(t, contract.HostDNSEnabled()) assert.True(t, contract.UseRSAServiceAccountKey()) } @@ -93,7 +93,7 @@ func TestContract1_7(t *testing.T) { assert.True(t, contract.SecretboxEncryptionSupported()) assert.True(t, contract.DiskQuotaSupportEnabled()) assert.True(t, contract.KubePrismEnabled()) - assert.True(t, contract.LocalDNSEnabled()) + assert.True(t, contract.HostDNSEnabled()) assert.True(t, contract.UseRSAServiceAccountKey()) } @@ -120,7 +120,7 @@ func TestContract1_6(t *testing.T) { assert.True(t, contract.SecretboxEncryptionSupported()) assert.True(t, contract.DiskQuotaSupportEnabled()) assert.True(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -147,7 +147,7 @@ func TestContract1_5(t *testing.T) { assert.True(t, contract.SecretboxEncryptionSupported()) assert.True(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -174,7 +174,7 @@ func TestContract1_4(t *testing.T) { assert.True(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -201,7 +201,7 @@ func TestContract1_3(t *testing.T) { assert.True(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -228,7 +228,7 @@ func TestContract1_2(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -255,7 +255,7 @@ func TestContract1_1(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -282,7 +282,7 @@ func TestContract1_0(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -309,7 +309,7 @@ func TestContract0_14(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -336,7 +336,7 @@ func TestContract0_13(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -363,7 +363,7 @@ func TestContract0_12(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -390,7 +390,7 @@ func TestContract0_11(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -417,7 +417,7 @@ func TestContract0_10(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -444,7 +444,7 @@ func TestContract0_9(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } @@ -471,6 +471,6 @@ func TestContract0_8(t *testing.T) { assert.False(t, contract.SecretboxEncryptionSupported()) assert.False(t, contract.DiskQuotaSupportEnabled()) assert.False(t, contract.KubePrismEnabled()) - assert.False(t, contract.LocalDNSEnabled()) + assert.False(t, contract.HostDNSEnabled()) assert.False(t, contract.UseRSAServiceAccountKey()) } diff --git a/pkg/machinery/config/generate/generate_test.go b/pkg/machinery/config/generate/generate_test.go index 2e2c40a9e1..d837a16ff5 100644 --- a/pkg/machinery/config/generate/generate_test.go +++ b/pkg/machinery/config/generate/generate_test.go @@ -177,3 +177,7 @@ func (m runtimeMode) String() string { func (m runtimeMode) RequiresInstall() bool { return m.requiresInstall } + +func (runtimeMode) InContainer() bool { + return false +} diff --git a/pkg/machinery/config/generate/init.go b/pkg/machinery/config/generate/init.go index db28bb40f1..dcb6cd4b1a 100644 --- a/pkg/machinery/config/generate/init.go +++ b/pkg/machinery/config/generate/init.go @@ -95,9 +95,10 @@ func (in *Input) init() ([]config.Document, error) { machine.MachineKubelet.KubeletDisableManifestsDirectory = pointer.To(true) } - if in.Options.VersionContract.LocalDNSEnabled() { + if in.Options.VersionContract.HostDNSEnabled() || in.Options.HostDNSForwardKubeDNSToHost.ValueOrZero() { machine.MachineFeatures.HostDNSSupport = &v1alpha1.HostDNSConfig{ - HostDNSEnabled: pointer.To(true), + HostDNSEnabled: pointer.To(true), + HostDNSForwardKubeDNSToHost: in.Options.HostDNSForwardKubeDNSToHost.Ptr(), } } diff --git a/pkg/machinery/config/generate/options.go b/pkg/machinery/config/generate/options.go index 2c27a217e7..9d383d3590 100644 --- a/pkg/machinery/config/generate/options.go +++ b/pkg/machinery/config/generate/options.go @@ -261,6 +261,15 @@ func WithSecretsBundle(bundle *secrets.Bundle) Option { } } +// WithHostDNSForwardKubeDNSToHost specifies whether to forward kube-dns to host. +func WithHostDNSForwardKubeDNSToHost(forward bool) Option { + return func(o *Options) error { + o.HostDNSForwardKubeDNSToHost = optional.Some(forward) + + return nil + } +} + // Options describes generate parameters. type Options struct { VersionContract *config.VersionContract @@ -301,6 +310,8 @@ type Options struct { KubePrismPort optional.Optional[int] + HostDNSForwardKubeDNSToHost optional.Optional[bool] + // Client options. Roles role.Set EndpointList []string diff --git a/pkg/machinery/config/generate/worker.go b/pkg/machinery/config/generate/worker.go index 7a70032314..51487fd1e7 100644 --- a/pkg/machinery/config/generate/worker.go +++ b/pkg/machinery/config/generate/worker.go @@ -96,9 +96,10 @@ func (in *Input) worker() ([]config.Document, error) { machine.MachineKubelet.KubeletDisableManifestsDirectory = pointer.To(true) } - if in.Options.VersionContract.LocalDNSEnabled() { + if in.Options.VersionContract.HostDNSEnabled() || in.Options.HostDNSForwardKubeDNSToHost.ValueOrZero() { machine.MachineFeatures.HostDNSSupport = &v1alpha1.HostDNSConfig{ - HostDNSEnabled: pointer.To(true), + HostDNSEnabled: pointer.To(true), + HostDNSForwardKubeDNSToHost: in.Options.HostDNSForwardKubeDNSToHost.Ptr(), } } diff --git a/pkg/machinery/config/types/network/rule_config_test.go b/pkg/machinery/config/types/network/rule_config_test.go index bcb40d6741..086f221c39 100644 --- a/pkg/machinery/config/types/network/rule_config_test.go +++ b/pkg/machinery/config/types/network/rule_config_test.go @@ -195,3 +195,7 @@ func (validationMode) String() string { func (validationMode) RequiresInstall() bool { return false } + +func (validationMode) InContainer() bool { + return false +} diff --git a/pkg/machinery/config/types/runtime/event_sink_test.go b/pkg/machinery/config/types/runtime/event_sink_test.go index 68b7a8208e..51cb5bba36 100644 --- a/pkg/machinery/config/types/runtime/event_sink_test.go +++ b/pkg/machinery/config/types/runtime/event_sink_test.go @@ -92,3 +92,7 @@ func (validationMode) String() string { func (validationMode) RequiresInstall() bool { return false } + +func (validationMode) InContainer() bool { + return false +} diff --git a/pkg/machinery/config/types/siderolink/siderolink_test.go b/pkg/machinery/config/types/siderolink/siderolink_test.go index 1cf50747a2..46ae5dac72 100644 --- a/pkg/machinery/config/types/siderolink/siderolink_test.go +++ b/pkg/machinery/config/types/siderolink/siderolink_test.go @@ -118,3 +118,7 @@ func (validationMode) String() string { func (validationMode) RequiresInstall() bool { return false } + +func (validationMode) InContainer() bool { + return false +} diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go index 5c57c8ce93..bdf4978e34 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_validation.go @@ -116,6 +116,17 @@ func (c *Config) Validate(mode validation.RuntimeMode, options ...validation.Opt } } + if mode.InContainer() { + // require that HostDNS features are enabled to passthrough container DNS to kube-dns + if !c.Machine().Features().HostDNS().Enabled() { + result = multierror.Append(result, errors.New("feature HostDNS should be enabled in container mode (.machine.features.hostDNS.enabled)")) + } + + if !c.Machine().Features().HostDNS().ForwardKubeDNSToHost() { + result = multierror.Append(result, errors.New("feature HostDNS should forward kube-dns to host in container mode (.machine.features.hostDNS.forwardKubeDNSToHost)")) + } + } + if t := c.Machine().Type(); t != machine.TypeUnknown && t.String() != c.MachineConfig.MachineType { warnings = append(warnings, fmt.Sprintf("use %q instead of %q for machine type", t.String(), c.MachineConfig.MachineType)) } diff --git a/pkg/machinery/config/types/v1alpha1/v1alpha1_validation_test.go b/pkg/machinery/config/types/v1alpha1/v1alpha1_validation_test.go index b2b7d4e953..249714357e 100644 --- a/pkg/machinery/config/types/v1alpha1/v1alpha1_validation_test.go +++ b/pkg/machinery/config/types/v1alpha1/v1alpha1_validation_test.go @@ -32,6 +32,10 @@ func (m runtimeMode) RequiresInstall() bool { return m.requiresInstall } +func (runtimeMode) InContainer() bool { + return false +} + func TestValidate(t *testing.T) { t.Parallel() diff --git a/pkg/machinery/config/validation/mode.go b/pkg/machinery/config/validation/mode.go index 6d57aab102..13605ed83f 100644 --- a/pkg/machinery/config/validation/mode.go +++ b/pkg/machinery/config/validation/mode.go @@ -10,4 +10,5 @@ import "fmt" type RuntimeMode interface { fmt.Stringer RequiresInstall() bool + InContainer() bool } diff --git a/pkg/provision/providers/docker/docker.go b/pkg/provision/providers/docker/docker.go index d5287b6748..758538b737 100644 --- a/pkg/provision/providers/docker/docker.go +++ b/pkg/provision/providers/docker/docker.go @@ -86,33 +86,11 @@ func (p *provisioner) Close() error { // GenOptions provides a list of additional config generate options. func (p *provisioner) GenOptions(networkReq provision.NetworkRequest) []generate.Option { - nameservers := make([]string, 0, len(networkReq.Nameservers)) - - hasV4 := false - hasV6 := false - - for _, subnet := range networkReq.CIDRs { - if subnet.Addr().Is6() { - hasV6 = true - } else { - hasV4 = true - } - } - - // filter nameservers by IPv4/IPv6 - for i := range networkReq.Nameservers { - if networkReq.Nameservers[i].Is6() && hasV6 { - nameservers = append(nameservers, networkReq.Nameservers[i].String()) - } else if networkReq.Nameservers[i].Is4() && hasV4 { - nameservers = append(nameservers, networkReq.Nameservers[i].String()) - } - } - return []generate.Option{ generate.WithNetworkOptions( v1alpha1.WithNetworkInterfaceIgnore(v1alpha1.IfaceByName("eth0")), - v1alpha1.WithNetworkNameservers(nameservers...), ), + generate.WithHostDNSForwardKubeDNSToHost(true), } }