feat: move host DNS config into ResolverConfig

This deprecates more `.machine.features`, allows host DNS to be enabled
in maintenance mode.

Fixes #12438

Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
This commit is contained in:
Andrey Smirnov 2026-04-22 17:58:41 +04:00
parent 96a8ecd1ee
commit 837a9ed077
No known key found for this signature in database
GPG Key ID: 322C6F63F594CE7C
39 changed files with 1444 additions and 508 deletions

View File

@ -19,6 +19,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
"github.com/siderolabs/talos/pkg/machinery/config/generate"
"github.com/siderolabs/talos/pkg/machinery/config/generate/secrets"
"github.com/siderolabs/talos/pkg/machinery/version"
"github.com/siderolabs/talos/pkg/provision"
)
@ -150,7 +151,8 @@ func TestCommonMaker_MachineConfig(t *testing.T) {
// assertConfigDefaultness makes sure the maker-generated machine configs are not different from default talos machine configs.
func assertConfigDefaultness[ExtraOps any](t *testing.T, cOps clusterops.Common, m makers.Maker[ExtraOps], desiredExtraGenOps []generate.Option, extraPatches ...configpatcher.Patch) {
var versionContract *config.VersionContract
versionContract, err := config.ParseContractFromVersion(version.Tag)
require.NoError(t, err)
secretsBundle, err := secrets.NewBundle(secrets.NewClock(), versionContract)
require.NoError(t, err)

View File

@ -45,6 +45,12 @@ Custom settings for cipher suites have been removed, as they are ignored when TL
title = "Default Installer Image"
description = """\
The default installer image has been updated to use the Image Factory.
"""
[notes.hostdnsconfig]
title = "Host DNS Configuration"
description = """\
HostDNS configuration was moved from the v1alpha1 config `.machine.features.hostDNS` field to the new `hostDNS` in the `ResolverConfig` document.
"""
[make_deps]

View File

@ -17,7 +17,7 @@ import (
"github.com/siderolabs/go-procfs/procfs"
"go.uber.org/zap"
talosconfig "github.com/siderolabs/talos/pkg/machinery/config"
cfgcfg "github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
@ -71,8 +71,6 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti
case <-r.EventCh():
}
var cfgProvider talosconfig.Config
r.StartTrackingOutputs()
cfg, err := safe.ReaderGetByID[*config.MachineConfig](ctx, r, config.ActiveID)
@ -80,8 +78,12 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti
if !state.IsNotFoundError(err) {
return fmt.Errorf("error getting config: %w", err)
}
} else if cfg.Config().Machine() != nil {
cfgProvider = cfg.Config()
}
var hostDNSConfig cfgcfg.NetworkHostDNSConfig
if cfg != nil {
hostDNSConfig = cfg.Config().NetworkHostDNSConfig()
}
newServiceAddrs := make([]netip.Addr, 0, 2)
@ -93,21 +95,27 @@ func (ctrl *HostDNSConfigController) Run(ctx context.Context, r controller.Runti
res.TypedSpec().ServiceHostDNSAddress = netip.Addr{}
if cfgProvider == nil {
if hostDNSConfig == nil {
res.TypedSpec().Enabled = false
return nil
}
res.TypedSpec().Enabled = cfgProvider.Machine().Features().HostDNS().Enabled()
res.TypedSpec().ResolveMemberNames = cfgProvider.Machine().Features().HostDNS().ResolveMemberNames()
res.TypedSpec().Enabled = hostDNSConfig.HostDNSEnabled()
res.TypedSpec().ResolveMemberNames = hostDNSConfig.ResolveMemberNames()
if !cfgProvider.Machine().Features().HostDNS().ForwardKubeDNSToHost() {
if !hostDNSConfig.ForwardKubeDNSToHost() {
return nil
}
var podCIDRs []string
if cfg.Config().Cluster() != nil {
podCIDRs = cfg.Config().Cluster().Network().PodCIDRs()
}
if slices.ContainsFunc(
cfgProvider.Cluster().Network().PodCIDRs(),
podCIDRs,
func(cidr string) bool { return netip.MustParsePrefix(cidr).Addr().Is4() },
) {
parsed := netip.MustParseAddr(constants.HostDNSAddress)

View File

@ -0,0 +1,263 @@
// 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 network_test
import (
"net/netip"
"net/url"
"testing"
"time"
"github.com/cosi-project/runtime/pkg/resource/rtestutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"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/pkg/machinery/config/container"
networkcfg "github.com/siderolabs/talos/pkg/machinery/config/types/network"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/config"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
)
type HostDNSConfigSuite struct {
ctest.DefaultSuite
}
func (suite *HostDNSConfigSuite) TestNoConfig() {
ctest.AssertResource(suite, network.HostDNSConfigID, func(r *network.HostDNSConfig, asrt *assert.Assertions) {
asrt.False(r.TypedSpec().Enabled)
asrt.Equal(
[]netip.AddrPort{netip.MustParseAddrPort("127.0.0.53:53")},
r.TypedSpec().ListenAddresses,
)
asrt.Equal(netip.Addr{}, r.TypedSpec().ServiceHostDNSAddress)
asrt.False(r.TypedSpec().ResolveMemberNames)
})
}
func (suite *HostDNSConfigSuite) TestLegacyConfigEnabled() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{ //nolint:staticcheck // testing legacy config
HostDNSConfigEnabled: new(true),
HostDNSResolveMemberNames: new(true),
},
},
},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{URL: u},
},
ClusterNetwork: &v1alpha1.ClusterNetworkConfig{
PodSubnet: []string{constants.DefaultIPv4PodNet},
},
},
},
),
)
suite.Create(cfg)
ctest.AssertResource(suite, network.HostDNSConfigID, func(r *network.HostDNSConfig, asrt *assert.Assertions) {
asrt.True(r.TypedSpec().Enabled)
asrt.Equal(
[]netip.AddrPort{netip.MustParseAddrPort("127.0.0.53:53")},
r.TypedSpec().ListenAddresses,
)
asrt.Equal(netip.Addr{}, r.TypedSpec().ServiceHostDNSAddress)
asrt.True(r.TypedSpec().ResolveMemberNames)
})
ctest.AssertNoResource[*network.AddressSpec](
suite,
network.LayeredID(network.ConfigOperator, network.AddressID("lo", netip.MustParsePrefix(constants.HostDNSAddress+"/32"))),
rtestutils.WithNamespace(network.ConfigNamespaceName),
)
}
func (suite *HostDNSConfigSuite) TestLegacyConfigForwardKubeDNSIPv4() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{ //nolint:staticcheck // testing legacy config
HostDNSConfigEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
},
},
},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{URL: u},
},
ClusterNetwork: &v1alpha1.ClusterNetworkConfig{
PodSubnet: []string{constants.DefaultIPv4PodNet, constants.DefaultIPv6PodNet},
},
},
},
),
)
suite.Create(cfg)
hostDNSAddr := netip.MustParseAddr(constants.HostDNSAddress)
ctest.AssertResource(suite, network.HostDNSConfigID, func(r *network.HostDNSConfig, asrt *assert.Assertions) {
asrt.True(r.TypedSpec().Enabled)
asrt.Equal(
[]netip.AddrPort{
netip.MustParseAddrPort("127.0.0.53:53"),
netip.AddrPortFrom(hostDNSAddr, 53),
},
r.TypedSpec().ListenAddresses,
)
asrt.Equal(hostDNSAddr, r.TypedSpec().ServiceHostDNSAddress)
})
addrPrefix := netip.PrefixFrom(hostDNSAddr, hostDNSAddr.BitLen())
ctest.AssertResource(
suite,
network.LayeredID(network.ConfigOperator, network.AddressID("lo", addrPrefix)),
func(r *network.AddressSpec, asrt *assert.Assertions) {
spec := r.TypedSpec()
asrt.Equal(addrPrefix, spec.Address)
asrt.Equal("lo", spec.LinkName)
asrt.Equal(nethelpers.FamilyInet4, spec.Family)
asrt.Equal(nethelpers.ScopeHost, spec.Scope)
asrt.Equal(nethelpers.AddressFlags(nethelpers.AddressPermanent), spec.Flags)
asrt.Equal(network.ConfigOperator, spec.ConfigLayer)
},
rtestutils.WithNamespace(network.ConfigNamespaceName),
)
}
func (suite *HostDNSConfigSuite) TestLegacyConfigForwardKubeDNSIPv6Only() {
u, err := url.Parse("https://foo:6443")
suite.Require().NoError(err)
cfg := config.NewMachineConfig(
container.NewV1Alpha1(
&v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{ //nolint:staticcheck // testing legacy config
HostDNSConfigEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
},
},
},
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{URL: u},
},
ClusterNetwork: &v1alpha1.ClusterNetworkConfig{
PodSubnet: []string{constants.DefaultIPv6PodNet},
},
},
},
),
)
suite.Create(cfg)
ctest.AssertResource(suite, network.HostDNSConfigID, func(r *network.HostDNSConfig, asrt *assert.Assertions) {
asrt.True(r.TypedSpec().Enabled)
asrt.Equal(
[]netip.AddrPort{netip.MustParseAddrPort("127.0.0.53:53")},
r.TypedSpec().ListenAddresses,
)
asrt.Equal(netip.Addr{}, r.TypedSpec().ServiceHostDNSAddress)
})
ctest.AssertNoResource[*network.AddressSpec](
suite,
network.LayeredID(network.ConfigOperator, network.AddressID("lo", netip.MustParsePrefix(constants.HostDNSAddress+"/32"))),
rtestutils.WithNamespace(network.ConfigNamespaceName),
)
}
func (suite *HostDNSConfigSuite) TestResolverConfigDocument() {
rc := networkcfg.NewResolverConfigV1Alpha1()
rc.ResolverHostDNS = networkcfg.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
HostDNSResolveMemberNames: new(true),
}
v1 := &v1alpha1.Config{
ConfigVersion: "v1alpha1",
MachineConfig: &v1alpha1.MachineConfig{},
ClusterConfig: &v1alpha1.ClusterConfig{
ClusterNetwork: &v1alpha1.ClusterNetworkConfig{
PodSubnet: []string{constants.DefaultIPv4PodNet},
},
},
}
ctr, err := container.New(v1, rc)
suite.Require().NoError(err)
suite.Create(config.NewMachineConfig(ctr))
hostDNSAddr := netip.MustParseAddr(constants.HostDNSAddress)
ctest.AssertResource(suite, network.HostDNSConfigID, func(r *network.HostDNSConfig, asrt *assert.Assertions) {
asrt.True(r.TypedSpec().Enabled)
asrt.True(r.TypedSpec().ResolveMemberNames)
asrt.Equal(
[]netip.AddrPort{
netip.MustParseAddrPort("127.0.0.53:53"),
netip.AddrPortFrom(hostDNSAddr, 53),
},
r.TypedSpec().ListenAddresses,
)
asrt.Equal(hostDNSAddr, r.TypedSpec().ServiceHostDNSAddress)
})
addrPrefix := netip.PrefixFrom(hostDNSAddr, hostDNSAddr.BitLen())
ctest.AssertResource(
suite,
network.LayeredID(network.ConfigOperator, network.AddressID("lo", addrPrefix)),
func(r *network.AddressSpec, asrt *assert.Assertions) {
asrt.Equal(addrPrefix, r.TypedSpec().Address)
asrt.Equal(nethelpers.FamilyInet4, r.TypedSpec().Family)
asrt.Equal("lo", r.TypedSpec().LinkName)
},
rtestutils.WithNamespace(network.ConfigNamespaceName),
)
}
func TestHostDNSConfigSuite(t *testing.T) {
t.Parallel()
suite.Run(t, &HostDNSConfigSuite{
DefaultSuite: ctest.DefaultSuite{
Timeout: 5 * time.Second,
AfterSetup: func(s *ctest.DefaultSuite) {
s.Require().NoError(s.Runtime().RegisterController(&netctrl.HostDNSConfigController{}))
},
},
})
}

View File

@ -207,8 +207,8 @@ func (ctrl *NfTablesChainConfigController) buildIngressChain(cfg *config.Machine
},
)
if cfg.Config().Machine() != nil && cfg.Config().Cluster() != nil {
if cfg.Config().Machine().Features().HostDNS().ForwardKubeDNSToHost() {
if hostDNSConfig := cfg.Config().NetworkHostDNSConfig(); hostDNSConfig != nil {
if hostDNSConfig.ForwardKubeDNSToHost() {
hostDNSIP := netip.MustParseAddr(constants.HostDNSAddress)
// allow traffic to host DNS

View File

@ -198,7 +198,7 @@ func (r *Runtime) CanApplyImmediate(cfg config.Provider) error {
if newConfig.MachineConfig.MachineFeatures != nil && currentConfig.MachineConfig.MachineFeatures != nil {
newConfig.MachineConfig.MachineFeatures.KubernetesTalosAPIAccessConfig = currentConfig.MachineConfig.MachineFeatures.KubernetesTalosAPIAccessConfig
newConfig.MachineConfig.MachineFeatures.KubePrismSupport = currentConfig.MachineConfig.MachineFeatures.KubePrismSupport
newConfig.MachineConfig.MachineFeatures.HostDNSSupport = currentConfig.MachineConfig.MachineFeatures.HostDNSSupport
newConfig.MachineConfig.MachineFeatures.HostDNSSupport = currentConfig.MachineConfig.MachineFeatures.HostDNSSupport //nolint:staticcheck // backwards compatibility
newConfig.MachineConfig.MachineFeatures.ImageCacheSupport = currentConfig.MachineConfig.MachineFeatures.ImageCacheSupport
newConfig.MachineConfig.MachineFeatures.FeatureNodeAddressSortAlgorithm = currentConfig.MachineConfig.MachineFeatures.FeatureNodeAddressSortAlgorithm
}

View File

@ -22,6 +22,7 @@ type Config interface { //nolint:interfacebloat
NetworkStaticHostConfig() []NetworkStaticHostConfig
NetworkHostnameConfig() NetworkHostnameConfig
NetworkResolverConfig() NetworkResolverConfig
NetworkHostDNSConfig() NetworkHostDNSConfig
NetworkTimeSyncConfig() NetworkTimeSyncConfig
NetworkKubeSpanConfig() NetworkKubeSpanConfig
NetworkCommonLinkConfigs() []NetworkCommonLinkConfig

View File

@ -378,7 +378,6 @@ type SystemDiskEncryption interface {
type Features interface {
KubernetesTalosAPIAccess() KubernetesTalosAPIAccess
DiskQuotaSupportEnabled() bool
HostDNS() HostDNS
KubePrism() KubePrism
ImageCache() ImageCache
NodeAddressSortAlgorithm() nethelpers.AddressSortAlgorithm
@ -397,13 +396,6 @@ type KubePrism interface {
Port() int
}
// HostDNS describes the host DNS configuration.
type HostDNS interface {
Enabled() bool
ForwardKubeDNSToHost() bool
ResolveMemberNames() bool
}
// ImageCache describes the image cache configuration.
type ImageCache interface {
LocalEnabled() bool

View File

@ -381,3 +381,10 @@ type NetworkRoutingRuleConfig interface {
FwMark() uint32
FwMask() uint32
}
// NetworkHostDNSConfig defines a host DNS configuration.
type NetworkHostDNSConfig interface {
HostDNSEnabled() bool
ForwardKubeDNSToHost() bool
ResolveMemberNames() bool
}

View File

@ -7,21 +7,17 @@ package container
import (
"bytes"
"context"
"errors"
"fmt"
"slices"
"strings"
"github.com/cosi-project/runtime/pkg/state"
"github.com/hashicorp/go-multierror"
"github.com/siderolabs/gen/xslices"
coreconfig "github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/config/validation"
)
// V1Alpha1ConflictValidator is the interface implemented by config documents which conflict with legacy v1alpha1 config.
@ -328,6 +324,23 @@ func (container *Container) NetworkResolverConfig() config.NetworkResolverConfig
return nil
}
// NetworkHostDNSConfig implements config.Config interface.
func (container *Container) NetworkHostDNSConfig() config.NetworkHostDNSConfig {
// first check if we have a dedicated document, and it is not empty
// for backwards compatibility, we will fall back to v1alpha1 if the ResolverConfig document does not have hostDNS enabled
matching := findMatchingDocs[config.NetworkHostDNSConfig](container.documents)
if len(matching) > 0 && matching[0].HostDNSEnabled() {
return matching[0]
}
// fallback to v1alpha1
if container.v1alpha1Config != nil {
return container.v1alpha1Config.NetworkHostDNSConfig()
}
return nil
}
// NetworkTimeSyncConfig implements config.Config interface.
func (container *Container) NetworkTimeSyncConfig() config.NetworkTimeSyncConfig {
// first check if we have a dedicated document
@ -577,90 +590,6 @@ func docID(doc config.Document) string {
return id
}
// Validate checks configuration and returns warnings and fatal errors (as multierror).
//
//nolint:gocyclo
func (container *Container) Validate(mode validation.RuntimeMode, opt ...validation.Option) ([]string, error) {
var (
warnings []string
err error
)
if container.v1alpha1Config != nil {
warnings, err = container.v1alpha1Config.Validate(mode, opt...)
if err != nil {
err = fmt.Errorf("v1alpha1.Config: %w", err)
}
}
var multiErr *multierror.Error
if err != nil {
multiErr = multierror.Append(multiErr, err)
}
for _, doc := range container.documents {
if validatableDoc, ok := doc.(config.Validator); ok {
docWarnings, docErr := validatableDoc.Validate(mode, opt...)
if docErr != nil {
docErr = fmt.Errorf("%s: %w", docID(doc), docErr)
}
warnings = append(warnings, docWarnings...)
multiErr = multierror.Append(multiErr, docErr)
}
}
// now cross-validate the config
if container.v1alpha1Config != nil {
for _, doc := range container.documents {
if conflictValidator, ok := doc.(V1Alpha1ConflictValidator); ok {
err := conflictValidator.V1Alpha1ConflictValidate(container.v1alpha1Config)
if err != nil {
multiErr = multierror.Append(multiErr, err)
}
}
}
}
return warnings, multiErr.ErrorOrNil()
}
// RuntimeValidate validates the config in the runtime context.
func (container *Container) RuntimeValidate(ctx context.Context, st state.State, mode validation.RuntimeMode, opt ...validation.Option) ([]string, error) {
var (
warnings []string
err error
)
if container.v1alpha1Config != nil {
warnings, err = container.v1alpha1Config.RuntimeValidate(ctx, st, mode, opt...)
if err != nil {
err = fmt.Errorf("v1alpha1.Config: %w", err)
}
}
var multiErr *multierror.Error
if err != nil {
multiErr = multierror.Append(multiErr, err)
}
for _, doc := range container.documents {
if validatableDoc, ok := doc.(config.RuntimeValidator); ok {
docWarnings, docErr := validatableDoc.RuntimeValidate(ctx, st, mode, opt...)
if docErr != nil {
docErr = fmt.Errorf("%s: %w", docID(doc), docErr)
}
warnings = append(warnings, docWarnings...)
multiErr = multierror.Append(multiErr, docErr)
}
}
return warnings, multiErr.ErrorOrNil()
}
// RedactSecrets returns a copy of the Provider with all secrets replaced with the given string.
func (container *Container) RedactSecrets(replacement string) coreconfig.Provider {
clone := container.clone()

View File

@ -8,7 +8,6 @@ import (
"net/url"
"testing"
"github.com/siderolabs/crypto/x509"
"github.com/siderolabs/gen/xtesting/must"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -23,8 +22,6 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config/types/runtime/extensions"
"github.com/siderolabs/talos/pkg/machinery/config/types/siderolink"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
blockres "github.com/siderolabs/talos/pkg/machinery/resources/block"
)
func TestNew(t *testing.T) {
@ -153,197 +150,6 @@ func TestPatchV1Alpha1(t *testing.T) {
assert.Equal(t, "https://siderolink.api/?jointoken=secret&user=alice", patchedCfg.SideroLink().APIUrl().String())
}
func TestValidate(t *testing.T) {
t.Parallel()
sideroLinkCfg := siderolink.NewConfigV1Alpha1()
sideroLinkCfg.APIUrlConfig.URL = must.Value(url.Parse("https://siderolink.api/?jointoken=secret&user=alice"))(t)
invalidSideroLinkCfg := siderolink.NewConfigV1Alpha1()
v1alpha1Cfg := &v1alpha1.Config{
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: must.Value(url.Parse("https://localhost:6443"))(t),
},
},
},
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "worker",
MachineCA: &x509.PEMEncodedCertificateAndKey{
Crt: []byte("cert"),
},
},
}
invalidV1alpha1Config := &v1alpha1.Config{}
for _, tt := range []struct {
name string
documents []config.Document
expectedError string
expecetedWarnings []string
}{
{
name: "empty",
},
{
name: "multi-doc",
documents: []config.Document{sideroLinkCfg, v1alpha1Cfg},
},
{
name: "only siderolink",
documents: []config.Document{sideroLinkCfg},
},
{
name: "only v1alpha1",
documents: []config.Document{v1alpha1Cfg},
},
{
name: "invalid siderolink",
documents: []config.Document{invalidSideroLinkCfg},
expectedError: "1 error occurred:\n\t* SideroLinkConfig: apiUrl is required\n\n",
},
{
name: "invalid v1alpha1",
documents: []config.Document{invalidV1alpha1Config},
expectedError: "1 error occurred:\n\t* v1alpha1.Config: 1 error occurred:\n\t* machine instructions are required\n\n\n\n",
},
{
name: "invalid multi-doc",
documents: []config.Document{invalidSideroLinkCfg, invalidV1alpha1Config},
expectedError: "2 errors occurred:\n\t* v1alpha1.Config: 1 error occurred:\n\t* machine instructions are required\n\n\n\t* SideroLinkConfig: apiUrl is required\n\n",
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctr, err := container.New(tt.documents...)
require.NoError(t, err)
warnings, err := ctr.Validate(validationMode{})
if tt.expectedError == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, tt.expectedError)
}
require.Equal(t, tt.expecetedWarnings, warnings)
})
}
}
func TestCrossValidateEncryption(t *testing.T) {
t.Parallel()
v1alpha1Cfg := &v1alpha1.Config{
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: must.Value(url.Parse("https://localhost:6443"))(t),
},
},
},
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "worker",
MachineCA: &x509.PEMEncodedCertificateAndKey{
Crt: []byte("cert"),
},
MachineSystemDiskEncryption: &v1alpha1.SystemDiskEncryptionConfig{
EphemeralPartition: &v1alpha1.EncryptionConfig{
EncryptionKeys: []*v1alpha1.EncryptionKey{
{
KeySlot: 1,
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "static-key",
},
},
},
},
},
},
}
defaultEphemeral := block.NewVolumeConfigV1Alpha1()
defaultEphemeral.MetaName = constants.EphemeralPartitionLabel
encryptedEphemeral := block.NewVolumeConfigV1Alpha1()
encryptedEphemeral.MetaName = constants.EphemeralPartitionLabel
encryptedEphemeral.EncryptionSpec = block.EncryptionSpec{
EncryptionProvider: blockres.EncryptionProviderLUKS2,
EncryptionKeys: []block.EncryptionKey{
{
KeySlot: 2,
KeyStatic: &block.EncryptionKeyStatic{
KeyData: "encrypted-static-key",
},
},
},
}
encryptedState := block.NewVolumeConfigV1Alpha1()
encryptedState.MetaName = constants.StatePartitionLabel
encryptedState.EncryptionSpec = block.EncryptionSpec{
EncryptionProvider: blockres.EncryptionProviderLUKS2,
EncryptionKeys: []block.EncryptionKey{
{
KeySlot: 3,
KeyTPM: &block.EncryptionKeyTPM{},
},
},
}
for _, tt := range []struct {
name string
documents []config.Document
expectedError string
expecetedWarnings []string
}{
{
name: "only v1alpha1",
documents: []config.Document{v1alpha1Cfg},
},
{
name: "v1alpha1 with no-conflict volumes",
documents: []config.Document{v1alpha1Cfg, defaultEphemeral, encryptedState},
},
{
name: "v1alpha1 with no-conflict volumes",
documents: []config.Document{v1alpha1Cfg, encryptedState},
},
{
name: "no v1alpha1",
documents: []config.Document{encryptedEphemeral, encryptedState},
},
{
name: "conflict on ephemeral encryption",
documents: []config.Document{v1alpha1Cfg, encryptedEphemeral},
expectedError: "1 error occurred:\n\t* system disk encryption for \"EPHEMERAL\" is configured in both v1alpha1.Config and VolumeConfig\n\n",
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctr, err := container.New(tt.documents...)
require.NoError(t, err)
warnings, err := ctr.Validate(validationMode{})
if tt.expectedError == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, tt.expectedError)
}
require.Equal(t, tt.expecetedWarnings, warnings)
})
}
}
func TestRunDefaultDHCPOperators(t *testing.T) {
t.Parallel()
@ -397,17 +203,3 @@ func TestRunDefaultDHCPOperators(t *testing.T) {
})
}
}
type validationMode struct{}
func (validationMode) String() string {
return ""
}
func (validationMode) RequiresInstall() bool {
return false
}
func (validationMode) InContainer() bool {
return false
}

View File

@ -0,0 +1,134 @@
// 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 container
import (
"context"
"fmt"
"github.com/cosi-project/runtime/pkg/state"
"github.com/hashicorp/go-multierror"
"github.com/siderolabs/talos/pkg/machinery/config"
"github.com/siderolabs/talos/pkg/machinery/config/validation"
)
// Validate checks configuration and returns warnings and fatal errors (as multierror).
//
// The validation first validates each individual document, then it does conflict validation of new
// documents with v1alpha1.Config (if it exists).
// Finally, whole container is validated according to the mode.
//
//nolint:gocyclo
func (container *Container) Validate(mode validation.RuntimeMode, opt ...validation.Option) ([]string, error) {
var (
warnings []string
err error
)
if container.v1alpha1Config != nil {
warnings, err = container.v1alpha1Config.Validate(mode, opt...)
if err != nil {
err = fmt.Errorf("v1alpha1.Config: %w", err)
}
}
var multiErr *multierror.Error
if err != nil {
multiErr = multierror.Append(multiErr, err)
}
for _, doc := range container.documents {
if validatableDoc, ok := doc.(config.Validator); ok {
docWarnings, docErr := validatableDoc.Validate(mode, opt...)
if docErr != nil {
docErr = fmt.Errorf("%s: %w", docID(doc), docErr)
}
warnings = append(warnings, docWarnings...)
multiErr = multierror.Append(multiErr, docErr)
}
}
// now cross-validate the config
if container.v1alpha1Config != nil {
for _, doc := range container.documents {
if conflictValidator, ok := doc.(V1Alpha1ConflictValidator); ok {
err := conflictValidator.V1Alpha1ConflictValidate(container.v1alpha1Config)
if err != nil {
multiErr = multierror.Append(multiErr, err)
}
}
}
}
if err := container.validateContainer(mode); err != nil {
multiErr = multierror.Append(multiErr, err)
}
return warnings, multiErr.ErrorOrNil()
}
// RuntimeValidate validates the config in the runtime context.
func (container *Container) RuntimeValidate(ctx context.Context, st state.State, mode validation.RuntimeMode, opt ...validation.Option) ([]string, error) {
var (
warnings []string
err error
)
if container.v1alpha1Config != nil {
warnings, err = container.v1alpha1Config.RuntimeValidate(ctx, st, mode, opt...)
if err != nil {
err = fmt.Errorf("v1alpha1.Config: %w", err)
}
}
var multiErr *multierror.Error
if err != nil {
multiErr = multierror.Append(multiErr, err)
}
for _, doc := range container.documents {
if validatableDoc, ok := doc.(config.RuntimeValidator); ok {
docWarnings, docErr := validatableDoc.RuntimeValidate(ctx, st, mode, opt...)
if docErr != nil {
docErr = fmt.Errorf("%s: %w", docID(doc), docErr)
}
warnings = append(warnings, docWarnings...)
multiErr = multierror.Append(multiErr, docErr)
}
}
return warnings, multiErr.ErrorOrNil()
}
// validateContainer validates the full configuration container.
//
// This validation is used to do validation which only makes sense for the full configuration (vs. individual documents).
func (container *Container) validateContainer(mode validation.RuntimeMode) error {
var errs error
if mode.InContainer() {
// in container mode, HostDNS must be enabled and forward KubeDNS to host must be enabled as well
hostDNSConfig := container.NetworkHostDNSConfig()
if hostDNSConfig == nil {
errs = multierror.Append(errs, fmt.Errorf("hostDNS config is required in container mode"))
} else {
if !hostDNSConfig.HostDNSEnabled() {
errs = multierror.Append(errs, fmt.Errorf("hostDNS must be enabled in container mode"))
}
if !hostDNSConfig.ForwardKubeDNSToHost() {
errs = multierror.Append(errs, fmt.Errorf("forwardKubeDNSToHost must be enabled in container mode"))
}
}
}
return errs
}

View File

@ -0,0 +1,344 @@
// 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 container_test
import (
"net/netip"
"net/url"
"testing"
"github.com/siderolabs/crypto/x509"
"github.com/siderolabs/gen/xtesting/must"
"github.com/stretchr/testify/require"
"github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/config/container"
"github.com/siderolabs/talos/pkg/machinery/config/types/block"
"github.com/siderolabs/talos/pkg/machinery/config/types/network"
"github.com/siderolabs/talos/pkg/machinery/config/types/siderolink"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
blockres "github.com/siderolabs/talos/pkg/machinery/resources/block"
)
func TestValidate(t *testing.T) {
t.Parallel()
sideroLinkCfg := siderolink.NewConfigV1Alpha1()
sideroLinkCfg.APIUrlConfig.URL = must.Value(url.Parse("https://siderolink.api/?jointoken=secret&user=alice"))(t)
invalidSideroLinkCfg := siderolink.NewConfigV1Alpha1()
v1alpha1Cfg := &v1alpha1.Config{
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: must.Value(url.Parse("https://localhost:6443"))(t),
},
},
},
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "worker",
MachineCA: &x509.PEMEncodedCertificateAndKey{
Crt: []byte("cert"),
},
},
}
invalidV1alpha1Config := &v1alpha1.Config{}
for _, tt := range []struct {
name string
documents []config.Document
expectedError string
expectedWarnings []string
}{
{
name: "empty",
},
{
name: "multi-doc",
documents: []config.Document{sideroLinkCfg, v1alpha1Cfg},
},
{
name: "only siderolink",
documents: []config.Document{sideroLinkCfg},
},
{
name: "only v1alpha1",
documents: []config.Document{v1alpha1Cfg},
},
{
name: "invalid siderolink",
documents: []config.Document{invalidSideroLinkCfg},
expectedError: "1 error occurred:\n\t* SideroLinkConfig: apiUrl is required\n\n",
},
{
name: "invalid v1alpha1",
documents: []config.Document{invalidV1alpha1Config},
expectedError: "1 error occurred:\n\t* v1alpha1.Config: 1 error occurred:\n\t* machine instructions are required\n\n\n\n",
},
{
name: "invalid multi-doc",
documents: []config.Document{invalidSideroLinkCfg, invalidV1alpha1Config},
expectedError: "2 errors occurred:\n\t* v1alpha1.Config: 1 error occurred:\n\t* machine instructions are required\n\n\n\t* SideroLinkConfig: apiUrl is required\n\n",
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctr, err := container.New(tt.documents...)
require.NoError(t, err)
warnings, err := ctr.Validate(validationMode{})
if tt.expectedError == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, tt.expectedError)
}
require.Equal(t, tt.expectedWarnings, warnings)
})
}
}
func TestCrossValidateEncryption(t *testing.T) {
t.Parallel()
v1alpha1Cfg := &v1alpha1.Config{
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: must.Value(url.Parse("https://localhost:6443"))(t),
},
},
},
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "worker",
MachineCA: &x509.PEMEncodedCertificateAndKey{
Crt: []byte("cert"),
},
MachineSystemDiskEncryption: &v1alpha1.SystemDiskEncryptionConfig{
EphemeralPartition: &v1alpha1.EncryptionConfig{
EncryptionKeys: []*v1alpha1.EncryptionKey{
{
KeySlot: 1,
KeyStatic: &v1alpha1.EncryptionKeyStatic{
KeyData: "static-key",
},
},
},
},
},
},
}
defaultEphemeral := block.NewVolumeConfigV1Alpha1()
defaultEphemeral.MetaName = constants.EphemeralPartitionLabel
encryptedEphemeral := block.NewVolumeConfigV1Alpha1()
encryptedEphemeral.MetaName = constants.EphemeralPartitionLabel
encryptedEphemeral.EncryptionSpec = block.EncryptionSpec{
EncryptionProvider: blockres.EncryptionProviderLUKS2,
EncryptionKeys: []block.EncryptionKey{
{
KeySlot: 2,
KeyStatic: &block.EncryptionKeyStatic{
KeyData: "encrypted-static-key",
},
},
},
}
encryptedState := block.NewVolumeConfigV1Alpha1()
encryptedState.MetaName = constants.StatePartitionLabel
encryptedState.EncryptionSpec = block.EncryptionSpec{
EncryptionProvider: blockres.EncryptionProviderLUKS2,
EncryptionKeys: []block.EncryptionKey{
{
KeySlot: 3,
KeyTPM: &block.EncryptionKeyTPM{},
},
},
}
for _, tt := range []struct {
name string
documents []config.Document
expectedError string
expectedWarnings []string
}{
{
name: "only v1alpha1",
documents: []config.Document{v1alpha1Cfg},
},
{
name: "v1alpha1 with no-conflict volumes",
documents: []config.Document{v1alpha1Cfg, defaultEphemeral, encryptedState},
},
{
name: "v1alpha1 with no-conflict volumes",
documents: []config.Document{v1alpha1Cfg, encryptedState},
},
{
name: "no v1alpha1",
documents: []config.Document{encryptedEphemeral, encryptedState},
},
{
name: "conflict on ephemeral encryption",
documents: []config.Document{v1alpha1Cfg, encryptedEphemeral},
expectedError: "1 error occurred:\n\t* system disk encryption for \"EPHEMERAL\" is configured in both v1alpha1.Config and VolumeConfig\n\n",
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctr, err := container.New(tt.documents...)
require.NoError(t, err)
warnings, err := ctr.Validate(validationMode{})
if tt.expectedError == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, tt.expectedError)
}
require.Equal(t, tt.expectedWarnings, warnings)
})
}
}
func TestValidateContainer(t *testing.T) {
t.Parallel()
sideroLinkCfg := siderolink.NewConfigV1Alpha1()
sideroLinkCfg.APIUrlConfig.URL = must.Value(url.Parse("https://siderolink.api/?jointoken=secret&user=alice"))(t)
v1alpha1Cfg := &v1alpha1.Config{
ClusterConfig: &v1alpha1.ClusterConfig{
ControlPlane: &v1alpha1.ControlPlaneConfig{
Endpoint: &v1alpha1.Endpoint{
URL: must.Value(url.Parse("https://localhost:6443"))(t),
},
},
},
MachineConfig: &v1alpha1.MachineConfig{
MachineType: "worker",
MachineCA: &x509.PEMEncodedCertificateAndKey{
Crt: []byte("cert"),
},
},
}
v1alpha1CfgHostDNS := v1alpha1Cfg.DeepCopy()
v1alpha1CfgHostDNS.MachineConfig.MachineFeatures = &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{ //nolint:staticcheck // testing legacy features
HostDNSConfigEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
},
}
resolverConfig := network.NewResolverConfigV1Alpha1()
resolverConfig.ResolverNameservers = []network.NameserverConfig{
{
Address: network.Addr{Addr: netip.MustParseAddr("1.1.1.1")},
},
}
hostDNSResolverConfig := network.NewResolverConfigV1Alpha1()
hostDNSResolverConfig.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
}
for _, tt := range []struct {
name string
documents []config.Document
inContainer bool
expectedError string
}{
{
name: "empty !container",
},
{
name: "empty container",
inContainer: true,
expectedError: "1 error occurred:\n\t* hostDNS config is required in container mode\n\n",
},
{
name: "empty v1alpha1 container",
documents: []config.Document{v1alpha1Cfg},
inContainer: true,
expectedError: "1 error occurred:\n\t* hostDNS config is required in container mode\n\n",
},
{
name: "just resolver in container",
documents: []config.Document{resolverConfig},
inContainer: true,
expectedError: "1 error occurred:\n\t* hostDNS config is required in container mode\n\n",
},
{
name: "hostDNS v1alpha1 container",
documents: []config.Document{v1alpha1CfgHostDNS},
inContainer: true,
},
{
name: "hostDNS v1alpha1 container plus multi-doc",
documents: []config.Document{v1alpha1CfgHostDNS, resolverConfig},
inContainer: true,
},
{
name: "just multi-doc with hostDNS",
documents: []config.Document{hostDNSResolverConfig},
inContainer: true,
},
{
name: "multi-doc with hostDNS and v1alpha1",
documents: []config.Document{hostDNSResolverConfig, v1alpha1Cfg},
inContainer: true,
},
} {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
ctr, err := container.New(tt.documents...)
require.NoError(t, err)
warnings, err := ctr.Validate(validationMode{inContainer: tt.inContainer})
if tt.expectedError == "" {
require.NoError(t, err)
} else {
require.EqualError(t, err, tt.expectedError)
}
require.Nil(t, warnings)
})
}
}
type validationMode struct {
inContainer bool
}
func (validationMode) String() string {
return ""
}
func (validationMode) RequiresInstall() bool {
return false
}
func (v validationMode) InContainer() bool {
return v.inContainer
}

View File

@ -209,3 +209,8 @@ func (contract *VersionContract) GrubUseUKICmdlineDefault() bool {
func (contract *VersionContract) KubeSpanMultidocConfig() bool {
return contract.Greater(TalosVersion1_12)
}
// HostDNSMultidocConfig returns true if version of Talos should use HostDNS multi-doc config.
func (contract *VersionContract) HostDNSMultidocConfig() bool {
return contract.Greater(TalosVersion1_13)
}

View File

@ -72,6 +72,7 @@ func TestContractCurrent(t *testing.T) {
assert.False(t, contract.PopulateClusterSANsFromEndpoint())
assert.True(t, contract.GrubUseUKICmdlineDefault())
assert.True(t, contract.KubeSpanMultidocConfig())
assert.True(t, contract.HostDNSMultidocConfig())
}
func TestContract1_14(t *testing.T) {
@ -102,6 +103,7 @@ func TestContract1_14(t *testing.T) {
assert.False(t, contract.PopulateClusterSANsFromEndpoint())
assert.True(t, contract.GrubUseUKICmdlineDefault())
assert.True(t, contract.KubeSpanMultidocConfig())
assert.True(t, contract.HostDNSMultidocConfig())
}
func TestContract1_13(t *testing.T) {
@ -132,6 +134,7 @@ func TestContract1_13(t *testing.T) {
assert.False(t, contract.PopulateClusterSANsFromEndpoint())
assert.True(t, contract.GrubUseUKICmdlineDefault())
assert.True(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_12(t *testing.T) {
@ -162,6 +165,7 @@ func TestContract1_12(t *testing.T) {
assert.False(t, contract.PopulateClusterSANsFromEndpoint())
assert.True(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_11(t *testing.T) {
@ -192,6 +196,7 @@ func TestContract1_11(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_10(t *testing.T) {
@ -222,6 +227,7 @@ func TestContract1_10(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_9(t *testing.T) {
@ -252,6 +258,7 @@ func TestContract1_9(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_8(t *testing.T) {
@ -282,6 +289,7 @@ func TestContract1_8(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_7(t *testing.T) {
@ -312,6 +320,7 @@ func TestContract1_7(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_6(t *testing.T) {
@ -342,6 +351,7 @@ func TestContract1_6(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_5(t *testing.T) {
@ -372,6 +382,7 @@ func TestContract1_5(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_4(t *testing.T) {
@ -402,6 +413,7 @@ func TestContract1_4(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_3(t *testing.T) {
@ -432,6 +444,7 @@ func TestContract1_3(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_2(t *testing.T) {
@ -462,6 +475,7 @@ func TestContract1_2(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_1(t *testing.T) {
@ -492,6 +506,7 @@ func TestContract1_1(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}
func TestContract1_0(t *testing.T) {
@ -522,4 +537,5 @@ func TestContract1_0(t *testing.T) {
assert.True(t, contract.PopulateClusterSANsFromEndpoint())
assert.False(t, contract.GrubUseUKICmdlineDefault())
assert.False(t, contract.KubeSpanMultidocConfig())
assert.False(t, contract.HostDNSMultidocConfig())
}

View File

@ -9,6 +9,7 @@ import (
"fmt"
"testing"
"github.com/siderolabs/gen/xslices"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
@ -18,6 +19,7 @@ import (
mc "github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/config/generate"
"github.com/siderolabs/talos/pkg/machinery/config/machine"
"github.com/siderolabs/talos/pkg/machinery/config/types/cri"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/role"
)
@ -148,14 +150,18 @@ func TestGenerateRegistryMirrorsOrder(t *testing.T) {
require.NoError(t, err)
cfg, err := input.Config(machine.TypeControlPlane)
require.NoError(t, err)
named, ok := cfg.Documents()[1].(mc.NamedDocument)
registryConfigs := xslices.Filter(cfg.Documents(), func(doc mc.Document) bool {
return doc.Kind() == cri.RegistryMirrorConfig
})
require.Len(t, registryConfigs, 2)
named, ok := registryConfigs[0].(mc.NamedDocument)
require.True(t, ok)
assert.Equal(t, "a.com", named.Name())
named, ok = cfg.Documents()[2].(mc.NamedDocument)
named, ok = registryConfigs[1].(mc.NamedDocument)
require.True(t, ok)
assert.Equal(t, "b.com", named.Name())
}

View File

@ -10,6 +10,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/config/machine"
"github.com/siderolabs/talos/pkg/machinery/config/types/network"
v1alpha1 "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
)
@ -79,9 +80,9 @@ func (in *Input) init() ([]config.Document, error) {
machine.MachineKubelet.KubeletDisableManifestsDirectory = new(true)
}
if in.Options.VersionContract.HostDNSEnabled() {
machine.MachineFeatures.HostDNSSupport = &v1alpha1.HostDNSConfig{
HostDNSEnabled: new(true),
if in.Options.VersionContract.HostDNSEnabled() && !in.Options.VersionContract.HostDNSMultidocConfig() {
machine.MachineFeatures.HostDNSSupport = &v1alpha1.HostDNSConfig{ //nolint:staticcheck // legacy configuration
HostDNSConfigEnabled: new(true),
HostDNSForwardKubeDNSToHost: ptrOrNil(in.Options.HostDNSForwardKubeDNSToHost.ValueOrZero() || in.Options.VersionContract.HostDNSForwardKubeDNSToHost()),
}
}
@ -210,6 +211,16 @@ func (in *Input) init() ([]config.Document, error) {
documents := []config.Document{v1alpha1Config}
if in.Options.VersionContract.HostDNSEnabled() && in.Options.VersionContract.HostDNSMultidocConfig() {
resolverConfig := network.NewResolverConfigV1Alpha1()
resolverConfig.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: ptrOrNil(in.Options.HostDNSForwardKubeDNSToHost.ValueOrZero() || in.Options.VersionContract.HostDNSForwardKubeDNSToHost()),
}
documents = append(documents, resolverConfig)
}
extraDocuments, err := in.generateRegistryConfigs(machine)
if err != nil {
return nil, fmt.Errorf("failed to generate registry configs: %w", err)

View File

@ -12,6 +12,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config/config"
"github.com/siderolabs/talos/pkg/machinery/config/machine"
"github.com/siderolabs/talos/pkg/machinery/config/types/network"
v1alpha1 "github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/constants"
)
@ -81,9 +82,9 @@ func (in *Input) worker() ([]config.Document, error) {
machine.MachineKubelet.KubeletDisableManifestsDirectory = new(true)
}
if in.Options.VersionContract.HostDNSEnabled() {
machine.MachineFeatures.HostDNSSupport = &v1alpha1.HostDNSConfig{
HostDNSEnabled: new(true),
if in.Options.VersionContract.HostDNSEnabled() && !in.Options.VersionContract.HostDNSMultidocConfig() {
machine.MachineFeatures.HostDNSSupport = &v1alpha1.HostDNSConfig{ //nolint:staticcheck // legacy config
HostDNSConfigEnabled: new(true),
HostDNSForwardKubeDNSToHost: ptrOrNil(in.Options.HostDNSForwardKubeDNSToHost.ValueOrZero() || in.Options.VersionContract.HostDNSForwardKubeDNSToHost()),
}
}
@ -143,6 +144,16 @@ func (in *Input) worker() ([]config.Document, error) {
documents := []config.Document{v1alpha1Config}
if in.Options.VersionContract.HostDNSEnabled() && in.Options.VersionContract.HostDNSMultidocConfig() {
resolverConfig := network.NewResolverConfigV1Alpha1()
resolverConfig.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: ptrOrNil(in.Options.HostDNSForwardKubeDNSToHost.ValueOrZero() || in.Options.VersionContract.HostDNSForwardKubeDNSToHost()),
}
documents = append(documents, resolverConfig)
}
extraDocuments, err := in.generateRegistryConfigs(machine)
if err != nil {
return nil, fmt.Errorf("failed to generate registry configs: %w", err)

View File

@ -2226,6 +2226,34 @@
],
"description": "HCloudVIPConfig is a config document to configure virtual IP using Hetzner Cloud APIs for announcement.\\nVirtual IP configuration should be used only on controlplane nodes to provide virtual IP for Kubernetes API server.\\nAny other use cases are not supported and may lead to unexpected behavior.\\nVirtual IP will be announced from only one node at a time using Hetzner Cloud APIs.\\n"
},
"network.HostDNSConfig": {
"properties": {
"enabled": {
"type": "boolean",
"title": "enabled",
"description": "Enable host DNS caching resolver.\n\nWhen enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.\nUpstream DNS servers for the host resolver are configured using the nameservers field in this config document.\n",
"markdownDescription": "Enable host DNS caching resolver.\n\nWhen enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.\nUpstream DNS servers for the host resolver are configured using the `nameservers` field in this config document.",
"x-intellij-html-description": "\u003cp\u003eEnable host DNS caching resolver.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.\nUpstream DNS servers for the host resolver are configured using the \u003ccode\u003enameservers\u003c/code\u003e field in this config document.\u003c/p\u003e\n"
},
"forwardKubeDNSToHost": {
"type": "boolean",
"title": "forwardKubeDNSToHost",
"description": "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).\n",
"markdownDescription": "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).",
"x-intellij-html-description": "\u003cp\u003eUse the host DNS resolver as upstream for Kubernetes CoreDNS pods.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).\u003c/p\u003e\n"
},
"resolveMemberNames": {
"type": "boolean",
"title": "resolveMemberNames",
"description": "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.\n",
"markdownDescription": "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.",
"x-intellij-html-description": "\u003cp\u003eResolve member hostnames using the host DNS resolver.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object",
"description": "HostDNSConfig represents host DNS configuration."
},
"network.HostnameConfigV1Alpha1": {
"properties": {
"apiVersion": {
@ -2705,6 +2733,13 @@
"description": "Configuration for search domains (in /etc/resolv.conf).\n\nThe default is to derive search domains from the hostname FQDN.\n",
"markdownDescription": "Configuration for search domains (in /etc/resolv.conf).\n\nThe default is to derive search domains from the hostname FQDN.",
"x-intellij-html-description": "\u003cp\u003eConfiguration for search domains (in /etc/resolv.conf).\u003c/p\u003e\n\n\u003cp\u003eThe default is to derive search domains from the hostname FQDN.\u003c/p\u003e\n"
},
"hostDNS": {
"$ref": "#/$defs/network.HostDNSConfig",
"title": "hostDNS",
"description": "Configuration for host DNS resolver.\n\nThis configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability.\n",
"markdownDescription": "Configuration for host DNS resolver.\n\nThis configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability.",
"x-intellij-html-description": "\u003cp\u003eConfiguration for host DNS resolver.\u003c/p\u003e\n\n\u003cp\u003eThis configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
@ -4674,13 +4709,6 @@
"markdownDescription": "KubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.",
"x-intellij-html-description": "\u003cp\u003eKubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.\u003c/p\u003e\n"
},
"hostDNS": {
"$ref": "#/$defs/v1alpha1.HostDNSConfig",
"title": "hostDNS",
"description": "Configures host DNS caching resolver.\n",
"markdownDescription": "Configures host DNS caching resolver.",
"x-intellij-html-description": "\u003cp\u003eConfigures host DNS caching resolver.\u003c/p\u003e\n"
},
"imageCache": {
"$ref": "#/$defs/v1alpha1.ImageCacheConfig",
"title": "imageCache",
@ -4724,34 +4752,6 @@
"type": "object",
"description": "FlannelCNIConfig represents the Flannel CNI configuration options."
},
"v1alpha1.HostDNSConfig": {
"properties": {
"enabled": {
"type": "boolean",
"title": "enabled",
"description": "Enable host DNS caching resolver.\n",
"markdownDescription": "Enable host DNS caching resolver.",
"x-intellij-html-description": "\u003cp\u003eEnable host DNS caching resolver.\u003c/p\u003e\n"
},
"forwardKubeDNSToHost": {
"type": "boolean",
"title": "forwardKubeDNSToHost",
"description": "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).\n",
"markdownDescription": "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).",
"x-intellij-html-description": "\u003cp\u003eUse the host DNS resolver as upstream for Kubernetes CoreDNS pods.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).\u003c/p\u003e\n"
},
"resolveMemberNames": {
"type": "boolean",
"title": "resolveMemberNames",
"description": "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.\n",
"markdownDescription": "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.",
"x-intellij-html-description": "\u003cp\u003eResolve member hostnames using the host DNS resolver.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object",
"description": "HostDNSConfig describes the configuration for the host DNS resolver."
},
"v1alpha1.ImageCacheConfig": {
"properties": {
"localEnabled": {

View File

@ -515,6 +515,18 @@ func (o *ResolverConfigV1Alpha1) DeepCopy() *ResolverConfigV1Alpha1 {
cp.ResolverSearchDomains.SearchDisableDefault = new(bool)
*cp.ResolverSearchDomains.SearchDisableDefault = *o.ResolverSearchDomains.SearchDisableDefault
}
if o.ResolverHostDNS.HostDNSEnabled != nil {
cp.ResolverHostDNS.HostDNSEnabled = new(bool)
*cp.ResolverHostDNS.HostDNSEnabled = *o.ResolverHostDNS.HostDNSEnabled
}
if o.ResolverHostDNS.HostDNSForwardKubeDNSToHost != nil {
cp.ResolverHostDNS.HostDNSForwardKubeDNSToHost = new(bool)
*cp.ResolverHostDNS.HostDNSForwardKubeDNSToHost = *o.ResolverHostDNS.HostDNSForwardKubeDNSToHost
}
if o.ResolverHostDNS.HostDNSResolveMemberNames != nil {
cp.ResolverHostDNS.HostDNSResolveMemberNames = new(bool)
*cp.ResolverHostDNS.HostDNSResolveMemberNames = *o.ResolverHostDNS.HostDNSResolveMemberNames
}
return &cp
}

View File

@ -1450,6 +1450,13 @@ func (ResolverConfigV1Alpha1) Doc() *encoder.Doc {
Description: "Configuration for search domains (in /etc/resolv.conf).\n\nThe default is to derive search domains from the hostname FQDN.",
Comments: [3]string{"" /* encoder.HeadComment */, "Configuration for search domains (in /etc/resolv.conf)." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "hostDNS",
Type: "HostDNSConfig",
Note: "",
Description: "Configuration for host DNS resolver.\n\nThis configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability.",
Comments: [3]string{"" /* encoder.HeadComment */, "Configuration for host DNS resolver." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}
@ -1457,6 +1464,8 @@ func (ResolverConfigV1Alpha1) Doc() *encoder.Doc {
doc.AddExample("", exampleResolverConfigV1Alpha2())
doc.AddExample("", exampleResolverConfigV1Alpha3())
return doc
}
@ -1519,6 +1528,45 @@ func (SearchDomainsConfig) Doc() *encoder.Doc {
return doc
}
func (HostDNSConfig) Doc() *encoder.Doc {
doc := &encoder.Doc{
Type: "HostDNSConfig",
Comments: [3]string{"" /* encoder.HeadComment */, "HostDNSConfig represents host DNS configuration." /* encoder.LineComment */, "" /* encoder.FootComment */},
Description: "HostDNSConfig represents host DNS configuration.",
AppearsIn: []encoder.Appearance{
{
TypeName: "ResolverConfigV1Alpha1",
FieldName: "hostDNS",
},
},
Fields: []encoder.Doc{
{
Name: "enabled",
Type: "bool",
Note: "",
Description: "Enable host DNS caching resolver.\n\nWhen enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.\nUpstream DNS servers for the host resolver are configured using the `nameservers` field in this config document.",
Comments: [3]string{"" /* encoder.HeadComment */, "Enable host DNS caching resolver." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "forwardKubeDNSToHost",
Type: "bool",
Note: "",
Description: "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).",
Comments: [3]string{"" /* encoder.HeadComment */, "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "resolveMemberNames",
Type: "bool",
Note: "",
Description: "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.",
Comments: [3]string{"" /* encoder.HeadComment */, "Resolve member hostnames using the host DNS resolver." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}
return doc
}
func (RoutingRuleConfigV1Alpha1) Doc() *encoder.Doc {
doc := &encoder.Doc{
Type: "RoutingRuleConfig",
@ -2155,6 +2203,7 @@ func GetFileDoc() *encoder.FileDoc {
ResolverConfigV1Alpha1{}.Doc(),
NameserverConfig{}.Doc(),
SearchDomainsConfig{}.Doc(),
HostDNSConfig{}.Doc(),
RoutingRuleConfigV1Alpha1{}.Doc(),
RuleConfigV1Alpha1{}.Doc(),
RulePortSelector{}.Doc(),

View File

@ -11,6 +11,7 @@ import (
"net/netip"
"slices"
"github.com/siderolabs/gen/value"
"github.com/siderolabs/gen/xslices"
"github.com/siderolabs/go-pointer"
@ -19,6 +20,7 @@ import (
"github.com/siderolabs/talos/pkg/machinery/config/internal/registry"
"github.com/siderolabs/talos/pkg/machinery/config/types/meta"
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
"github.com/siderolabs/talos/pkg/machinery/config/validation"
)
// ResolverKind is a ResolverConfig document kind.
@ -38,6 +40,8 @@ func init() {
// Check interfaces.
var (
_ config.NetworkResolverConfig = &ResolverConfigV1Alpha1{}
_ config.NetworkHostDNSConfig = &ResolverConfigV1Alpha1{}
_ config.Validator = &ResolverConfigV1Alpha1{}
_ container.V1Alpha1ConflictValidator = &ResolverConfigV1Alpha1{}
)
@ -46,6 +50,7 @@ var (
// examples:
// - value: exampleResolverConfigV1Alpha1()
// - value: exampleResolverConfigV1Alpha2()
// - value: exampleResolverConfigV1Alpha3()
// alias: ResolverConfig
// schemaRoot: true
// schemaMeta: v1alpha1/ResolverConfig
@ -66,6 +71,11 @@ type ResolverConfigV1Alpha1 struct {
//
// The default is to derive search domains from the hostname FQDN.
ResolverSearchDomains SearchDomainsConfig `yaml:"searchDomains,omitempty"`
// description: |
// Configuration for host DNS resolver.
//
// This configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability.
ResolverHostDNS HostDNSConfig `yaml:"hostDNS,omitempty"`
}
// NameserverConfig represents a single nameserver configuration.
@ -101,6 +111,28 @@ type SearchDomainsConfig struct {
SearchDisableDefault *bool `yaml:"disableDefault,omitempty"`
}
// HostDNSConfig represents host DNS configuration.
type HostDNSConfig struct {
// description: |
// Enable host DNS caching resolver.
//
// When enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.
// Upstream DNS servers for the host resolver are configured using the `nameservers` field in this config document.
HostDNSEnabled *bool `yaml:"enabled,omitempty"`
// description: |
// Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.
//
// When enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of
// using configured upstream DNS resolvers directly).
HostDNSForwardKubeDNSToHost *bool `yaml:"forwardKubeDNSToHost,omitempty"`
// description: |
// Resolve member hostnames using the host DNS resolver.
//
// When enabled, cluster member hostnames and node names are resolved using the host DNS resolver.
// This requires service discovery to be enabled.
HostDNSResolveMemberNames *bool `yaml:"resolveMemberNames,omitempty"`
}
// NewResolverConfigV1Alpha1 creates a new ResolverConfig config document.
func NewResolverConfigV1Alpha1() *ResolverConfigV1Alpha1 {
return &ResolverConfigV1Alpha1{
@ -137,6 +169,17 @@ func exampleResolverConfigV1Alpha2() *ResolverConfigV1Alpha1 {
return cfg
}
func exampleResolverConfigV1Alpha3() *ResolverConfigV1Alpha1 {
cfg := NewResolverConfigV1Alpha1()
cfg.ResolverHostDNS = HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
HostDNSResolveMemberNames: new(true),
}
return cfg
}
// Clone implements config.Document interface.
func (s *ResolverConfigV1Alpha1) Clone() config.Document {
return s.DeepCopy()
@ -156,9 +199,34 @@ func (s *ResolverConfigV1Alpha1) V1Alpha1ConflictValidate(v1alpha1Cfg *v1alpha1.
return errors.New(".machine.network.disableSearchDomain is already set in v1alpha1 config")
}
if !value.IsZero(s.ResolverHostDNS) {
if v1alpha1Cfg.NetworkHostDNSConfig() != nil {
return errors.New(".machine.features.hostDNS is already set in v1alpha1 config")
}
}
return nil
}
// Validate implements config.Validator interface.
func (s *ResolverConfigV1Alpha1) Validate(validation.RuntimeMode, ...validation.Option) ([]string, error) {
var errs error
if !value.IsZero(s.ResolverHostDNS) {
if !s.HostDNSEnabled() {
if s.ForwardKubeDNSToHost() {
errs = errors.Join(errs, errors.New("hostDNS.forwardKubeDNSToHost cannot be enabled when hostDNS.enabled is false"))
}
if s.ResolveMemberNames() {
errs = errors.Join(errs, errors.New("hostDNS.resolveMemberNames cannot be enabled when hostDNS.enabled is false"))
}
}
}
return nil, errs
}
// Resolvers implements NetworkResolverConfig interface.
func (s *ResolverConfigV1Alpha1) Resolvers() []netip.Addr {
return xslices.Map(s.ResolverNameservers, func(ns NameserverConfig) netip.Addr {
@ -175,3 +243,18 @@ func (s *ResolverConfigV1Alpha1) SearchDomains() []string {
func (s *ResolverConfigV1Alpha1) DisableSearchDomain() bool {
return pointer.SafeDeref(s.ResolverSearchDomains.SearchDisableDefault)
}
// HostDNSEnabled implements NetworkHostDNSConfig interface.
func (s *ResolverConfigV1Alpha1) HostDNSEnabled() bool {
return pointer.SafeDeref(s.ResolverHostDNS.HostDNSEnabled)
}
// ForwardKubeDNSToHost implements NetworkHostDNSConfig interface.
func (s *ResolverConfigV1Alpha1) ForwardKubeDNSToHost() bool {
return pointer.SafeDeref(s.ResolverHostDNS.HostDNSForwardKubeDNSToHost)
}
// ResolveMemberNames implements NetworkHostDNSConfig interface.
func (s *ResolverConfigV1Alpha1) ResolveMemberNames() bool {
return pointer.SafeDeref(s.ResolverHostDNS.HostDNSResolveMemberNames)
}

View File

@ -22,6 +22,9 @@ import (
//go:embed testdata/resolverconfig.yaml
var expectedResolverConfigDocument []byte
//go:embed testdata/resolverconfig_with_hostdns.yaml
var expectedResolverConfigDocumentWithHostDNS []byte
func TestResolverConfigMarshalStability(t *testing.T) {
t.Parallel()
@ -47,6 +50,29 @@ func TestResolverConfigMarshalStability(t *testing.T) {
assert.Equal(t, expectedResolverConfigDocument, marshaled)
}
func TestResolverConfigMarshalStabilityWithHostDNS(t *testing.T) {
t.Parallel()
cfg := network.NewResolverConfigV1Alpha1()
cfg.ResolverNameservers = []network.NameserverConfig{
{
Address: network.Addr{Addr: netip.MustParseAddr("10.0.0.1")},
},
}
cfg.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
HostDNSResolveMemberNames: new(false),
}
marshaled, err := encoder.NewEncoder(cfg, encoder.WithComments(encoder.CommentsDisabled)).Encode()
require.NoError(t, err)
t.Log(string(marshaled))
assert.Equal(t, expectedResolverConfigDocumentWithHostDNS, marshaled)
}
func TestResolverConfigUnmarshal(t *testing.T) {
t.Parallel()
@ -76,7 +102,7 @@ func TestResolverConfigUnmarshal(t *testing.T) {
}, docs[0])
}
func TestResolverV1Alpha1Validate(t *testing.T) {
func TestResolverV1Alpha1ConflictValidate(t *testing.T) {
t.Parallel()
for _, test := range []struct {
@ -130,6 +156,57 @@ func TestResolverV1Alpha1Validate(t *testing.T) {
expectedError: ".machine.network.disableSearchDomain is already set in v1alpha1 config",
},
{
name: "v1alpha1 hostDNS and resolver hostDNS set",
v1alpha1Cfg: &v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{ //nolint:staticcheck // testing legacy features
HostDNSConfigEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
},
},
},
},
cfg: func() *network.ResolverConfigV1Alpha1 {
cfg := network.NewResolverConfigV1Alpha1()
cfg.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
}
return cfg
},
expectedError: ".machine.features.hostDNS is already set in v1alpha1 config",
},
{
name: "v1alpha1 hostDNS and no resolver hostDNS set",
v1alpha1Cfg: &v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{ //nolint:staticcheck // testing legacy features
HostDNSConfigEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
},
},
},
},
cfg: network.NewResolverConfigV1Alpha1,
},
{
name: "v1alpha1 no hostDNS and resolver hostDNS set",
v1alpha1Cfg: &v1alpha1.Config{},
cfg: func() *network.ResolverConfigV1Alpha1 {
cfg := network.NewResolverConfigV1Alpha1()
cfg.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
}
return cfg
},
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
@ -143,3 +220,73 @@ func TestResolverV1Alpha1Validate(t *testing.T) {
})
}
}
func TestResolverV1Alpha1Validate(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
cfg func() *network.ResolverConfigV1Alpha1
expectedError string
}{
{
name: "empty",
cfg: network.NewResolverConfigV1Alpha1,
},
{
name: "forwardKubeDNSToHost true but HostDNSEnabled false",
cfg: func() *network.ResolverConfigV1Alpha1 {
cfg := network.NewResolverConfigV1Alpha1()
cfg.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(false),
HostDNSForwardKubeDNSToHost: new(true),
}
return cfg
},
expectedError: "hostDNS.forwardKubeDNSToHost cannot be enabled when hostDNS.enabled is false",
},
{
name: "resolveMemberNames true but HostDNSEnabled false",
cfg: func() *network.ResolverConfigV1Alpha1 {
cfg := network.NewResolverConfigV1Alpha1()
cfg.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(false),
HostDNSResolveMemberNames: new(true),
}
return cfg
},
expectedError: "hostDNS.resolveMemberNames cannot be enabled when hostDNS.enabled is false",
},
{
name: "hostDNS config valid",
cfg: func() *network.ResolverConfigV1Alpha1 {
cfg := network.NewResolverConfigV1Alpha1()
cfg.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
HostDNSResolveMemberNames: new(false),
}
return cfg
},
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
warnings, err := test.cfg().Validate(validationMode{})
assert.Nil(t, warnings)
if test.expectedError != "" {
assert.EqualError(t, err, test.expectedError)
} else {
assert.NoError(t, err)
}
})
}
}

View File

@ -0,0 +1,8 @@
apiVersion: v1alpha1
kind: ResolverConfig
nameservers:
- address: 10.0.0.1
hostDNS:
enabled: true
forwardKubeDNSToHost: true
resolveMemberNames: false

View File

@ -20,9 +20,6 @@ machine:
kubePrism:
enabled: true
port: 7445
hostDNS:
enabled: true
forwardKubeDNSToHost: true
nodeLabels:
node.kubernetes.io/exclude-from-external-load-balancers: ""
cluster:
@ -89,5 +86,11 @@ cluster:
key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSU03Q2VnMk1GQW5TM3ROMzV6QTc0aFZ3VElkTkthK0ZwUHlYVERCdU4wVFlvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFNmxTeTNTekRRRmdBTHNlSXR5UU1paTVaSVJkVTFGUmMzcEZ3b3g1QUE1VHdjZ0VVQ0xaNApwMTJSNGp3ZGozWXhqbmxLYW9GY3o3QVR5ME5mWTdMVWt3PT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
---
apiVersion: v1alpha1
kind: ResolverConfig
hostDNS:
enabled: true
forwardKubeDNSToHost: true
---
apiVersion: v1alpha1
kind: HostnameConfig
auto: stable

View File

@ -20,9 +20,6 @@ machine:
kubePrism:
enabled: true
port: 7445
hostDNS:
enabled: true
forwardKubeDNSToHost: true
cluster:
id: 0raF93qnkMvF-FZNuvyGozXNdLiT2FOWSlyBaW4PR-w=
secret: pofHbABZq7VXuObsdLdy/bHmz6hlMHZ3p8+6WKrv1ic=
@ -47,5 +44,11 @@ cluster:
service: {}
---
apiVersion: v1alpha1
kind: ResolverConfig
hostDNS:
enabled: true
forwardKubeDNSToHost: true
---
apiVersion: v1alpha1
kind: HostnameConfig
auto: stable

View File

@ -34,9 +34,6 @@ machine:
kubePrism:
enabled: true
port: 7445
hostDNS:
enabled: true
forwardKubeDNSToHost: true
nodeLabels:
node.kubernetes.io/exclude-from-external-load-balancers: ""
cluster:
@ -112,6 +109,12 @@ cluster:
allowSchedulingOnControlPlanes: true
---
apiVersion: v1alpha1
kind: ResolverConfig
hostDNS:
enabled: true
forwardKubeDNSToHost: true
---
apiVersion: v1alpha1
kind: RegistryMirrorConfig
name: ghcr.io
endpoints:

View File

@ -34,9 +34,6 @@ machine:
kubePrism:
enabled: true
port: 7445
hostDNS:
enabled: true
forwardKubeDNSToHost: true
cluster:
id: 0raF93qnkMvF-FZNuvyGozXNdLiT2FOWSlyBaW4PR-w=
secret: pofHbABZq7VXuObsdLdy/bHmz6hlMHZ3p8+6WKrv1ic=
@ -65,6 +62,12 @@ cluster:
service: {}
---
apiVersion: v1alpha1
kind: ResolverConfig
hostDNS:
enabled: true
forwardKubeDNSToHost: true
---
apiVersion: v1alpha1
kind: RegistryMirrorConfig
name: ghcr.io
endpoints:

View File

@ -21,15 +21,6 @@ func (f *FeaturesConfig) DiskQuotaSupportEnabled() bool {
return pointer.SafeDeref(f.DiskQuotaSupport)
}
// HostDNS implements config.Features interface.
func (f *FeaturesConfig) HostDNS() config.HostDNS {
if f.HostDNSSupport == nil {
return &HostDNSConfig{}
}
return f.HostDNSSupport
}
// KubePrism implements config.Features interface.
func (f *FeaturesConfig) KubePrism() config.KubePrism {
if f.KubePrismSupport == nil {
@ -78,17 +69,17 @@ func (a *KubePrism) Port() int {
return a.ServerPort
}
// Enabled implements config.HostDNS.
func (h *HostDNSConfig) Enabled() bool {
return pointer.SafeDeref(h.HostDNSEnabled)
// HostDNSEnabled implements config.NetworkHostDNSConfig interface.
func (h *HostDNSConfig) HostDNSEnabled() bool {
return pointer.SafeDeref(h.HostDNSConfigEnabled)
}
// ForwardKubeDNSToHost implements config.HostDNS.
// ForwardKubeDNSToHost implements config.NetworkHostDNSConfig interface.
func (h *HostDNSConfig) ForwardKubeDNSToHost() bool {
return pointer.SafeDeref(h.HostDNSForwardKubeDNSToHost)
}
// ResolveMemberNames implements config.HostDNS.
// ResolveMemberNames implements config.NetworkHostDNSConfig interface.
func (h *HostDNSConfig) ResolveMemberNames() bool {
return pointer.SafeDeref(h.HostDNSResolveMemberNames)
}

View File

@ -99,3 +99,12 @@ func (c *Config) NetworkKubeSpanConfig() config.NetworkKubeSpanConfig {
return c.MachineConfig.MachineNetwork.NetworkKubeSpan
}
// NetworkHostDNSConfig implements the config.NetworkHostDNSConfig interface.
func (c *Config) NetworkHostDNSConfig() config.NetworkHostDNSConfig {
if c.MachineConfig == nil || c.MachineConfig.MachineFeatures == nil || c.MachineConfig.MachineFeatures.HostDNSSupport == nil {
return nil
}
return c.MachineConfig.MachineFeatures.HostDNSSupport
}

View File

@ -556,3 +556,141 @@ func TestKubeSpanBridging(t *testing.T) {
})
}
}
func TestHostDNSConfigBridging(t *testing.T) {
t.Parallel()
for _, test := range []struct {
name string
cfg func(*testing.T) config.Config
expectedHostDNSConfigExists bool
expectedHostDNSEnabled bool
}{
{
name: "v1alpha1 empty",
cfg: func(*testing.T) config.Config {
return container.NewV1Alpha1(&v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{},
})
},
expectedHostDNSConfigExists: false,
},
{
name: "v1alpha1 hostDNS enabled",
cfg: func(*testing.T) config.Config {
return container.NewV1Alpha1(&v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{ //nolint:staticcheck // testing legacy features
HostDNSConfigEnabled: new(true),
},
},
},
})
},
expectedHostDNSConfigExists: true,
expectedHostDNSEnabled: true,
},
{
name: "resolver config hostDNS enabled",
cfg: func(*testing.T) config.Config {
resolverCfg := network.NewResolverConfigV1Alpha1()
resolverCfg.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
HostDNSResolveMemberNames: new(true),
}
c, err := container.New(
resolverCfg,
)
require.NoError(t, err)
return c
},
expectedHostDNSConfigExists: true,
expectedHostDNSEnabled: true,
},
{
name: "v1alpha1 empty and resolver config hostDNS enabled",
cfg: func(*testing.T) config.Config {
v1alpha1Cfg := &v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{},
}
resolverCfg := network.NewResolverConfigV1Alpha1()
resolverCfg.ResolverHostDNS = network.HostDNSConfig{
HostDNSEnabled: new(true),
HostDNSForwardKubeDNSToHost: new(true),
HostDNSResolveMemberNames: new(true),
}
c, err := container.New(
v1alpha1Cfg,
resolverCfg,
)
require.NoError(t, err)
return c
},
expectedHostDNSConfigExists: true,
expectedHostDNSEnabled: true,
},
{
name: "v1alpha1 hostDNS enabled and resolver empty",
cfg: func(*testing.T) config.Config {
v1alpha1Cfg := &v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{
MachineFeatures: &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{ //nolint:staticcheck // testing legacy features
HostDNSConfigEnabled: new(true),
},
},
},
}
resolverCfg := network.NewResolverConfigV1Alpha1()
c, err := container.New(
v1alpha1Cfg,
resolverCfg,
)
require.NoError(t, err)
return c
},
expectedHostDNSConfigExists: true,
expectedHostDNSEnabled: true,
},
} {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
cfg := test.cfg(t)
hostDNSConfig := cfg.NetworkHostDNSConfig()
if !test.expectedHostDNSConfigExists {
require.Nil(t, hostDNSConfig)
return
}
require.NotNil(t, hostDNSConfig)
assert.Equal(t, test.expectedHostDNSEnabled, hostDNSConfig.HostDNSEnabled())
})
}
}

View File

@ -2394,8 +2394,9 @@ type FeaturesConfig struct {
// KubePrism - local proxy/load balancer on defined port that will distribute
// requests to all API servers in the cluster.
KubePrismSupport *KubePrism `yaml:"kubePrism,omitempty"`
// description: |
// Configures host DNS caching resolver.
// docgen:nodoc
//
// Deprecated: Use ResolverConfig document instead.
HostDNSSupport *HostDNSConfig `yaml:"hostDNS,omitempty"`
// description: |
// Enable Image Cache feature.
@ -2441,10 +2442,14 @@ type KubernetesTalosAPIAccessConfig struct {
}
// HostDNSConfig describes the configuration for the host DNS resolver.
//
// Deprecated: Use ResolverConfig document instead.
//
// docgen:nodoc
type HostDNSConfig struct {
// description: |
// Enable host DNS caching resolver.
HostDNSEnabled *bool `yaml:"enabled,omitempty"`
HostDNSConfigEnabled *bool `yaml:"enabled,omitempty"`
// description: |
// Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.
//

View File

@ -1880,13 +1880,7 @@ func (FeaturesConfig) Doc() *encoder.Doc {
Description: "KubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.",
Comments: [3]string{"" /* encoder.HeadComment */, "KubePrism - local proxy/load balancer on defined port that will distribute" /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "hostDNS",
Type: "HostDNSConfig",
Note: "",
Description: "Configures host DNS caching resolver.",
Comments: [3]string{"" /* encoder.HeadComment */, "Configures host DNS caching resolver." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{},
{
Name: "imageCache",
Type: "ImageCacheConfig",
@ -2009,45 +2003,6 @@ func (KubernetesTalosAPIAccessConfig) Doc() *encoder.Doc {
return doc
}
func (HostDNSConfig) Doc() *encoder.Doc {
doc := &encoder.Doc{
Type: "HostDNSConfig",
Comments: [3]string{"" /* encoder.HeadComment */, "HostDNSConfig describes the configuration for the host DNS resolver." /* encoder.LineComment */, "" /* encoder.FootComment */},
Description: "HostDNSConfig describes the configuration for the host DNS resolver.",
AppearsIn: []encoder.Appearance{
{
TypeName: "FeaturesConfig",
FieldName: "hostDNS",
},
},
Fields: []encoder.Doc{
{
Name: "enabled",
Type: "bool",
Note: "",
Description: "Enable host DNS caching resolver.",
Comments: [3]string{"" /* encoder.HeadComment */, "Enable host DNS caching resolver." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "forwardKubeDNSToHost",
Type: "bool",
Note: "",
Description: "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).",
Comments: [3]string{"" /* encoder.HeadComment */, "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
{
Name: "resolveMemberNames",
Type: "bool",
Note: "",
Description: "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.",
Comments: [3]string{"" /* encoder.HeadComment */, "Resolve member hostnames using the host DNS resolver." /* encoder.LineComment */, "" /* encoder.FootComment */},
},
},
}
return doc
}
func (VolumeMountConfig) Doc() *encoder.Doc {
doc := &encoder.Doc{
Type: "VolumeMountConfig",
@ -2459,7 +2414,6 @@ func GetFileDoc() *encoder.FileDoc {
KubePrism{}.Doc(),
ImageCacheConfig{}.Doc(),
KubernetesTalosAPIAccessConfig{}.Doc(),
HostDNSConfig{}.Doc(),
VolumeMountConfig{}.Doc(),
ClusterInlineManifest{}.Doc(),
ClusterDiscoveryConfig{}.Doc(),

View File

@ -117,19 +117,10 @@ 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.NetworkHostDNSConfig() != nil {
if c.NetworkHostDNSConfig().ForwardKubeDNSToHost() && !c.NetworkHostDNSConfig().HostDNSEnabled() {
result = multierror.Append(result, errors.New("feature hostDNS.forwardKubeDNSToHost requires hostDNS.enabled to be true (.machine.features.hostDNS)"))
}
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 c.Machine().Features().HostDNS().ForwardKubeDNSToHost() && !c.Machine().Features().HostDNS().Enabled() {
result = multierror.Append(result, errors.New("feature hostDNS.forwardKubeDNSToHost requires hostDNS.enabled to be true (.machine.features.hostDNS)"))
}
if t := c.Machine().Type(); t != machine.TypeUnknown && t.String() != c.MachineConfig.MachineType {

View File

@ -2020,7 +2020,7 @@ func TestValidate(t *testing.T) {
},
MachineFeatures: &v1alpha1.FeaturesConfig{
HostDNSSupport: &v1alpha1.HostDNSConfig{
HostDNSEnabled: new(false),
HostDNSConfigEnabled: new(false),
HostDNSForwardKubeDNSToHost: new(true),
},
},

View File

@ -1114,8 +1114,8 @@ func (in *FlannelCNIConfig) DeepCopy() *FlannelCNIConfig {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *HostDNSConfig) DeepCopyInto(out *HostDNSConfig) {
*out = *in
if in.HostDNSEnabled != nil {
in, out := &in.HostDNSEnabled, &out.HostDNSEnabled
if in.HostDNSConfigEnabled != nil {
in, out := &in.HostDNSConfigEnabled, &out.HostDNSConfigEnabled
*out = new(bool)
**out = **in
}

View File

@ -35,11 +35,22 @@ searchDomains:
disableDefault: true # Disable default search domain configuration from hostname FQDN.
{{< /highlight >}}
{{< highlight yaml >}}
apiVersion: v1alpha1
kind: ResolverConfig
# Configuration for host DNS resolver.
hostDNS:
enabled: true # Enable host DNS caching resolver.
forwardKubeDNSToHost: true # Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.
resolveMemberNames: true # Resolve member hostnames using the host DNS resolver.
{{< /highlight >}}
| Field | Type | Description | Value(s) |
|-------|------|-------------|----------|
|`nameservers` |<a href="#ResolverConfig.nameservers.">[]NameserverConfig</a> |A list of nameservers (DNS servers) to use for resolving domain names.<br><br>Nameservers are used to resolve domain names on the host, and they are also<br>propagated to Kubernetes DNS (CoreDNS) for use by pods running on the cluster.<br><br>This overrides any nameservers obtained via DHCP or platform configuration.<br>Default configuration is to use 1.1.1.1 and 8.8.8.8 as nameservers. | |
|`searchDomains` |<a href="#ResolverConfig.searchDomains">SearchDomainsConfig</a> |Configuration for search domains (in /etc/resolv.conf).<br><br>The default is to derive search domains from the hostname FQDN. | |
|`hostDNS` |<a href="#ResolverConfig.hostDNS">HostDNSConfig</a> |Configuration for host DNS resolver.<br><br>This configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability. | |
@ -79,5 +90,23 @@ SearchDomainsConfig represents search domains configuration.
## hostDNS {#ResolverConfig.hostDNS}
HostDNSConfig represents host DNS configuration.
| Field | Type | Description | Value(s) |
|-------|------|-------------|----------|
|`enabled` |bool |Enable host DNS caching resolver.<br><br>When enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.<br>Upstream DNS servers for the host resolver are configured using the `nameservers` field in this config document. | |
|`forwardKubeDNSToHost` |bool |Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.<br><br>When enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of<br>using configured upstream DNS resolvers directly). | |
|`resolveMemberNames` |bool |Resolve member hostnames using the host DNS resolver.<br><br>When enabled, cluster member hostnames and node names are resolved using the host DNS resolver.<br>This requires service discovery to be enabled. | |

View File

@ -681,7 +681,6 @@ kubernetesTalosAPIAccess:
{{< /highlight >}}</details> | |
|`diskQuotaSupport` |bool |Enable XFS project quota support for EPHEMERAL partition and user disks.<br>Also enables kubelet tracking of ephemeral disk usage in the kubelet via quota. | |
|`kubePrism` |<a href="#Config.machine.features.kubePrism">KubePrism</a> |KubePrism - local proxy/load balancer on defined port that will distribute<br>requests to all API servers in the cluster. | |
|`hostDNS` |<a href="#Config.machine.features.hostDNS">HostDNSConfig</a> |Configures host DNS caching resolver. | |
|`imageCache` |<a href="#Config.machine.features.imageCache">ImageCacheConfig</a> |Enable Image Cache feature. | |
|`nodeAddressSortAlgorithm` |string |Select the node address sort algorithm.<br>The 'v1' algorithm sorts addresses by the address itself.<br>The 'v2' algorithm prefers more specific prefixes.<br>If unset, defaults to 'v1'. | |
@ -736,24 +735,6 @@ KubePrism describes the configuration for the KubePrism load balancer.
#### hostDNS {#Config.machine.features.hostDNS}
HostDNSConfig describes the configuration for the host DNS resolver.
| Field | Type | Description | Value(s) |
|-------|------|-------------|----------|
|`enabled` |bool |Enable host DNS caching resolver. | |
|`forwardKubeDNSToHost` |bool |Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.<br><br>When enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of<br>using configured upstream DNS resolvers directly). | |
|`resolveMemberNames` |bool |Resolve member hostnames using the host DNS resolver.<br><br>When enabled, cluster member hostnames and node names are resolved using the host DNS resolver.<br>This requires service discovery to be enabled. | |
#### imageCache {#Config.machine.features.imageCache}
ImageCacheConfig describes the configuration for the Image Cache feature.

View File

@ -2226,6 +2226,34 @@
],
"description": "HCloudVIPConfig is a config document to configure virtual IP using Hetzner Cloud APIs for announcement.\\nVirtual IP configuration should be used only on controlplane nodes to provide virtual IP for Kubernetes API server.\\nAny other use cases are not supported and may lead to unexpected behavior.\\nVirtual IP will be announced from only one node at a time using Hetzner Cloud APIs.\\n"
},
"network.HostDNSConfig": {
"properties": {
"enabled": {
"type": "boolean",
"title": "enabled",
"description": "Enable host DNS caching resolver.\n\nWhen enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.\nUpstream DNS servers for the host resolver are configured using the nameservers field in this config document.\n",
"markdownDescription": "Enable host DNS caching resolver.\n\nWhen enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.\nUpstream DNS servers for the host resolver are configured using the `nameservers` field in this config document.",
"x-intellij-html-description": "\u003cp\u003eEnable host DNS caching resolver.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, a local DNS caching resolver is deployed on the host to improve DNS resolution performance and reliability.\nUpstream DNS servers for the host resolver are configured using the \u003ccode\u003enameservers\u003c/code\u003e field in this config document.\u003c/p\u003e\n"
},
"forwardKubeDNSToHost": {
"type": "boolean",
"title": "forwardKubeDNSToHost",
"description": "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).\n",
"markdownDescription": "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).",
"x-intellij-html-description": "\u003cp\u003eUse the host DNS resolver as upstream for Kubernetes CoreDNS pods.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).\u003c/p\u003e\n"
},
"resolveMemberNames": {
"type": "boolean",
"title": "resolveMemberNames",
"description": "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.\n",
"markdownDescription": "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.",
"x-intellij-html-description": "\u003cp\u003eResolve member hostnames using the host DNS resolver.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object",
"description": "HostDNSConfig represents host DNS configuration."
},
"network.HostnameConfigV1Alpha1": {
"properties": {
"apiVersion": {
@ -2705,6 +2733,13 @@
"description": "Configuration for search domains (in /etc/resolv.conf).\n\nThe default is to derive search domains from the hostname FQDN.\n",
"markdownDescription": "Configuration for search domains (in /etc/resolv.conf).\n\nThe default is to derive search domains from the hostname FQDN.",
"x-intellij-html-description": "\u003cp\u003eConfiguration for search domains (in /etc/resolv.conf).\u003c/p\u003e\n\n\u003cp\u003eThe default is to derive search domains from the hostname FQDN.\u003c/p\u003e\n"
},
"hostDNS": {
"$ref": "#/$defs/network.HostDNSConfig",
"title": "hostDNS",
"description": "Configuration for host DNS resolver.\n\nThis configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability.\n",
"markdownDescription": "Configuration for host DNS resolver.\n\nThis configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability.",
"x-intellij-html-description": "\u003cp\u003eConfiguration for host DNS resolver.\u003c/p\u003e\n\n\u003cp\u003eThis configures a local DNS caching resolver on the host to improve DNS resolution performance and reliability.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
@ -4674,13 +4709,6 @@
"markdownDescription": "KubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.",
"x-intellij-html-description": "\u003cp\u003eKubePrism - local proxy/load balancer on defined port that will distribute\nrequests to all API servers in the cluster.\u003c/p\u003e\n"
},
"hostDNS": {
"$ref": "#/$defs/v1alpha1.HostDNSConfig",
"title": "hostDNS",
"description": "Configures host DNS caching resolver.\n",
"markdownDescription": "Configures host DNS caching resolver.",
"x-intellij-html-description": "\u003cp\u003eConfigures host DNS caching resolver.\u003c/p\u003e\n"
},
"imageCache": {
"$ref": "#/$defs/v1alpha1.ImageCacheConfig",
"title": "imageCache",
@ -4724,34 +4752,6 @@
"type": "object",
"description": "FlannelCNIConfig represents the Flannel CNI configuration options."
},
"v1alpha1.HostDNSConfig": {
"properties": {
"enabled": {
"type": "boolean",
"title": "enabled",
"description": "Enable host DNS caching resolver.\n",
"markdownDescription": "Enable host DNS caching resolver.",
"x-intellij-html-description": "\u003cp\u003eEnable host DNS caching resolver.\u003c/p\u003e\n"
},
"forwardKubeDNSToHost": {
"type": "boolean",
"title": "forwardKubeDNSToHost",
"description": "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).\n",
"markdownDescription": "Use the host DNS resolver as upstream for Kubernetes CoreDNS pods.\n\nWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).",
"x-intellij-html-description": "\u003cp\u003eUse the host DNS resolver as upstream for Kubernetes CoreDNS pods.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, CoreDNS pods use host DNS server as the upstream DNS (instead of\nusing configured upstream DNS resolvers directly).\u003c/p\u003e\n"
},
"resolveMemberNames": {
"type": "boolean",
"title": "resolveMemberNames",
"description": "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.\n",
"markdownDescription": "Resolve member hostnames using the host DNS resolver.\n\nWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.",
"x-intellij-html-description": "\u003cp\u003eResolve member hostnames using the host DNS resolver.\u003c/p\u003e\n\n\u003cp\u003eWhen enabled, cluster member hostnames and node names are resolved using the host DNS resolver.\nThis requires service discovery to be enabled.\u003c/p\u003e\n"
}
},
"additionalProperties": false,
"type": "object",
"description": "HostDNSConfig describes the configuration for the host DNS resolver."
},
"v1alpha1.ImageCacheConfig": {
"properties": {
"localEnabled": {