Skip to content

Commit

Permalink
feat: use container DNS when in container mode
Browse files Browse the repository at this point in the history
More specifically, pick up `/etc/resolv.conf` contents by default when
in container mode, and use that as a base resolver for the host DNS.

Fixes #8303

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
(cherry picked from commit 3433fa1)
  • Loading branch information
smira committed Apr 19, 2024
1 parent 7758144 commit d821322
Show file tree
Hide file tree
Showing 36 changed files with 327 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions internal/app/machined/pkg/controllers/config/acquire.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 4 additions & 0 deletions internal/app/machined/pkg/controllers/config/acquire_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
22 changes: 2 additions & 20 deletions internal/app/machined/pkg/controllers/network/dns_resolve_cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}

Expand Down Expand Up @@ -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{
Expand All @@ -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{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
7 changes: 5 additions & 2 deletions internal/app/machined/pkg/controllers/network/etcfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
15 changes: 9 additions & 6 deletions internal/app/machined/pkg/controllers/network/etcfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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))

Expand All @@ -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",
},
)
Expand All @@ -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",
},
)
Expand All @@ -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",
},
)
Expand All @@ -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",
},
)
Expand All @@ -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",
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
12 changes: 10 additions & 2 deletions internal/app/machined/pkg/controllers/network/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 6 additions & 1 deletion internal/app/machined/pkg/controllers/network/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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,
},
))
},
},
})
Expand Down
5 changes: 5 additions & 0 deletions internal/app/machined/pkg/runtime/mode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package container

import (
"bytes"
"context"
"encoding/base64"
"log"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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"),
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit d821322

Please sign in to comment.