feat: allow configuring etcd listen addresses

This introduces new configuration settings to configure
advertised/listen subnets. For backwards compatibility when using no
settings or old 'subnet' argument, etcd still listens on all addresses.

If new `advertisedSubnets` is being used, this automatically limits etcd
listen addresses to the same value. `listenSubnets` can be configured
also explicitly e.g. to listen on additional addresses for some other
scenarios (e.g. accessing etcd from outside of the cluster).

See #5668

One more thing left (for a separate PR) is to update etcd advertised
URLs on the fly.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
This commit is contained in:
Andrey Smirnov 2022-08-04 23:43:48 +04:00
parent 4c3485ae3f
commit dce923f747
No known key found for this signature in database
GPG Key ID: 7B26396447AB6DFD
16 changed files with 492 additions and 144 deletions

View File

@ -93,11 +93,8 @@ func (ctrl *ConfigController) Run(ctx context.Context, r controller.Runtime, log
} }
if err = safe.WriterModify(ctx, r, etcd.NewConfig(etcd.NamespaceName, etcd.ConfigID), func(status *etcd.Config) error { if err = safe.WriterModify(ctx, r, etcd.NewConfig(etcd.NamespaceName, etcd.ConfigID), func(status *etcd.Config) error {
if machineConfig.Config().Cluster().Etcd().Subnet() != "" { status.TypedSpec().AdvertiseValidSubnets = machineConfig.Config().Cluster().Etcd().AdvertisedSubnets()
status.TypedSpec().ValidSubnets = []string{machineConfig.Config().Cluster().Etcd().Subnet()} status.TypedSpec().ListenValidSubnets = machineConfig.Config().Cluster().Etcd().ListenSubnets()
} else {
status.TypedSpec().ValidSubnets = []string{"0.0.0.0/0", "::/0"}
}
status.TypedSpec().Image = machineConfig.Config().Cluster().Etcd().Image() status.TypedSpec().Image = machineConfig.Config().Cluster().Etcd().Image()
status.TypedSpec().ExtraArgs = machineConfig.Config().Cluster().Etcd().ExtraArgs() status.TypedSpec().ExtraArgs = machineConfig.Config().Cluster().Etcd().ExtraArgs()

View File

@ -40,15 +40,73 @@ func (suite *ConfigSuite) TestReconcile() {
machineType.SetMachineType(machine.TypeControlPlane) machineType.SetMachineType(machine.TypeControlPlane)
suite.Require().NoError(suite.State().Create(suite.Ctx(), machineType)) suite.Require().NoError(suite.State().Create(suite.Ctx(), machineType))
cfg := &v1alpha1.Config{ for _, tt := range []struct {
ClusterConfig: &v1alpha1.ClusterConfig{ name string
EtcdConfig: &v1alpha1.EtcdConfig{ etcdConfig *v1alpha1.EtcdConfig
expectedConfig etcd.ConfigSpec
}{
{
name: "default config",
etcdConfig: &v1alpha1.EtcdConfig{
ContainerImage: "foo/bar:v1.0.0",
},
expectedConfig: etcd.ConfigSpec{
Image: "foo/bar:v1.0.0",
ExtraArgs: map[string]string{},
AdvertiseValidSubnets: nil,
ListenValidSubnets: nil,
},
},
{
name: "legacy subnet",
etcdConfig: &v1alpha1.EtcdConfig{
ContainerImage: "foo/bar:v1.0.0", ContainerImage: "foo/bar:v1.0.0",
EtcdExtraArgs: map[string]string{ EtcdExtraArgs: map[string]string{
"arg": "value", "arg": "value",
}, },
EtcdSubnet: "10.0.0.0/8", EtcdSubnet: "10.0.0.0/8",
}, },
expectedConfig: etcd.ConfigSpec{
Image: "foo/bar:v1.0.0",
ExtraArgs: map[string]string{
"arg": "value",
},
AdvertiseValidSubnets: []string{"10.0.0.0/8"},
ListenValidSubnets: nil,
},
},
{
name: "advertised subnets",
etcdConfig: &v1alpha1.EtcdConfig{
ContainerImage: "foo/bar:v1.0.0",
EtcdAdvertisedSubnets: []string{"10.0.0.0/8", "192.168.0.0/24"},
},
expectedConfig: etcd.ConfigSpec{
Image: "foo/bar:v1.0.0",
ExtraArgs: map[string]string{},
AdvertiseValidSubnets: []string{"10.0.0.0/8", "192.168.0.0/24"},
ListenValidSubnets: []string{"10.0.0.0/8", "192.168.0.0/24"},
},
},
{
name: "advertised and listen subnets",
etcdConfig: &v1alpha1.EtcdConfig{
ContainerImage: "foo/bar:v1.0.0",
EtcdAdvertisedSubnets: []string{"10.0.0.0/8", "192.168.0.0/24"},
EtcdListenSubnets: []string{"10.0.0.0/8"},
},
expectedConfig: etcd.ConfigSpec{
Image: "foo/bar:v1.0.0",
ExtraArgs: map[string]string{},
AdvertiseValidSubnets: []string{"10.0.0.0/8", "192.168.0.0/24"},
ListenValidSubnets: []string{"10.0.0.0/8"},
},
},
} {
suite.Run(tt.name, func() {
cfg := &v1alpha1.Config{
ClusterConfig: &v1alpha1.ClusterConfig{
EtcdConfig: tt.etcdConfig,
}, },
} }
@ -63,8 +121,10 @@ func (suite *ConfigSuite) TestReconcile() {
return return
} }
assert.Equal("foo/bar:v1.0.0", etcdConfig.TypedSpec().Image) assert.Equal(tt.expectedConfig, *etcdConfig.TypedSpec())
assert.Equal(map[string]string{"arg": "value"}, etcdConfig.TypedSpec().ExtraArgs)
assert.Equal([]string{"10.0.0.0/8"}, etcdConfig.TypedSpec().ValidSubnets)
})) }))
suite.Require().NoError(suite.State().Destroy(suite.Ctx(), machineConfig.Metadata()))
})
}
} }

View File

@ -7,7 +7,7 @@ package etcd
import ( import (
"context" "context"
"fmt" "fmt"
"net/netip" stdnet "net"
"github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/controller"
"github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/resource"
@ -16,7 +16,10 @@ import (
"github.com/siderolabs/go-pointer" "github.com/siderolabs/go-pointer"
"github.com/talos-systems/net" "github.com/talos-systems/net"
"go.uber.org/zap" "go.uber.org/zap"
"inet.af/netaddr"
"github.com/talos-systems/talos/pkg/machinery/generic/slices"
"github.com/talos-systems/talos/pkg/machinery/nethelpers"
"github.com/talos-systems/talos/pkg/machinery/resources/etcd" "github.com/talos-systems/talos/pkg/machinery/resources/etcd"
"github.com/talos-systems/talos/pkg/machinery/resources/k8s" "github.com/talos-systems/talos/pkg/machinery/resources/k8s"
"github.com/talos-systems/talos/pkg/machinery/resources/network" "github.com/talos-systems/talos/pkg/machinery/resources/network"
@ -48,7 +51,7 @@ func (ctrl *SpecController) Inputs() []controller.Input {
{ {
Namespace: network.NamespaceName, Namespace: network.NamespaceName,
Type: network.NodeAddressType, Type: network.NodeAddressType,
ID: pointer.To(network.FilteredNodeAddressID(network.NodeAddressCurrentID, k8s.NodeAddressFilterNoK8s)), ID: pointer.To(network.FilteredNodeAddressID(network.NodeAddressRoutedID, k8s.NodeAddressFilterNoK8s)),
Kind: controller.InputWeak, Kind: controller.InputWeak,
}, },
} }
@ -93,50 +96,96 @@ func (ctrl *SpecController) Run(ctx context.Context, r controller.Runtime, logge
return fmt.Errorf("error getting hostname status: %w", err) return fmt.Errorf("error getting hostname status: %w", err)
} }
cidrs := make([]string, 0, len(etcdConfig.TypedSpec().ValidSubnets)+len(etcdConfig.TypedSpec().ExcludeSubnets)) nodeAddrs, err := safe.ReaderGet[*network.NodeAddress](
ctx,
cidrs = append(cidrs, etcdConfig.TypedSpec().ValidSubnets...) r,
resource.NewMetadata(
for _, subnet := range etcdConfig.TypedSpec().ExcludeSubnets { network.NamespaceName,
cidrs = append(cidrs, "!"+subnet) network.NodeAddressType,
} network.FilteredNodeAddressID(network.NodeAddressRoutedID, k8s.NodeAddressFilterNoK8s),
resource.VersionUndefined,
// we have trigger on NodeAddresses, but we don't use them directly as they contain ),
// some addresses which are not assigned to the node (like AWS ExternalIP). )
// we need to find solution for that later, for now just pull addresses directly
ips, err := net.IPAddrs()
if err != nil { if err != nil {
return fmt.Errorf("error listing IPs: %w", err) if state.IsNotFoundError(err) {
continue
} }
listenAddress := netip.IPv4Unspecified() return fmt.Errorf("error getting addresses: %w", err)
}
for _, ip := range ips { addrs := nodeAddrs.TypedSpec().IPs()
if ip.To4() == nil {
listenAddress = netip.IPv6Unspecified() // need at least a single address
if len(addrs) == 0 {
continue
}
advertisedCIDRs := make([]string, 0, len(etcdConfig.TypedSpec().AdvertiseValidSubnets)+len(etcdConfig.TypedSpec().AdvertiseExcludeSubnets))
advertisedCIDRs = append(advertisedCIDRs, etcdConfig.TypedSpec().AdvertiseValidSubnets...)
advertisedCIDRs = append(advertisedCIDRs, slices.Map(etcdConfig.TypedSpec().AdvertiseExcludeSubnets, func(cidr string) string { return "!" + cidr })...)
listenCIDRs := make([]string, 0, len(etcdConfig.TypedSpec().ListenValidSubnets)+len(etcdConfig.TypedSpec().ListenExcludeSubnets))
listenCIDRs = append(listenCIDRs, etcdConfig.TypedSpec().ListenValidSubnets...)
listenCIDRs = append(listenCIDRs, slices.Map(etcdConfig.TypedSpec().ListenExcludeSubnets, func(cidr string) string { return "!" + cidr })...)
defaultListenAddress := netaddr.IPv4(0, 0, 0, 0)
loopbackAddress := netaddr.IPv4(127, 0, 0, 1)
for _, ip := range addrs {
if ip.Is6() {
defaultListenAddress = netaddr.IPv6Unspecified()
loopbackAddress = netaddr.MustParseIP("::1")
break break
} }
} }
// we use stdnet.IP here to re-use already existing functions in talos-systems/net var (
// once talos-systems/net is migrated to netaddr or netip, we can use it here advertisedIPs []netaddr.IP
ips = net.IPFilter(ips, network.NotSideroLinkStdIP) listenPeerIPs []netaddr.IP
listenClientIPs []netaddr.IP
)
ips, err = net.FilterIPs(ips, cidrs) if len(advertisedCIDRs) > 0 {
// TODO: this should eventually be rewritten with `net.FilterIPs` on netaddrs, but for now we'll keep same code and do the conversion.
var stdIPs []stdnet.IP
stdIPs, err = net.FilterIPs(nethelpers.MapNetAddrToStd(addrs), advertisedCIDRs)
if err != nil { if err != nil {
return fmt.Errorf("error filtering IPs: %w", err) return fmt.Errorf("error filtering IPs: %w", err)
} }
if len(ips) == 0 { advertisedIPs = nethelpers.MapStdToNetAddr(stdIPs)
} else {
// if advertise subnet is not set, advertise the first address
advertisedIPs = []netaddr.IP{addrs[0]}
}
if len(listenCIDRs) > 0 {
// TODO: this should eventually be rewritten with `net.FilterIPs` on netaddrs, but for now we'll keep same code and do the conversion.
var stdIPs []stdnet.IP
stdIPs, err = net.FilterIPs(nethelpers.MapNetAddrToStd(addrs), listenCIDRs)
if err != nil {
return fmt.Errorf("error filtering IPs: %w", err)
}
listenPeerIPs = nethelpers.MapStdToNetAddr(stdIPs)
listenClientIPs = append([]netaddr.IP{loopbackAddress}, listenPeerIPs...)
} else {
listenPeerIPs = []netaddr.IP{defaultListenAddress}
listenClientIPs = []netaddr.IP{defaultListenAddress}
}
if len(advertisedIPs) == 0 || len(listenPeerIPs) == 0 {
continue continue
} }
if err = safe.WriterModify(ctx, r, etcd.NewSpec(etcd.NamespaceName, etcd.SpecID), func(status *etcd.Spec) error { if err = safe.WriterModify(ctx, r, etcd.NewSpec(etcd.NamespaceName, etcd.SpecID), func(status *etcd.Spec) error {
status.TypedSpec().AdvertisedAddress, _ = netip.AddrFromSlice(ips[0]) status.TypedSpec().AdvertisedAddresses = advertisedIPs
status.TypedSpec().AdvertisedAddress = status.TypedSpec().AdvertisedAddress.Unmap() status.TypedSpec().ListenClientAddresses = listenClientIPs
status.TypedSpec().ListenAddress = listenAddress status.TypedSpec().ListenPeerAddresses = listenPeerIPs
status.TypedSpec().Name = hostnameStatus.TypedSpec().Hostname status.TypedSpec().Name = hostnameStatus.TypedSpec().Hostname
status.TypedSpec().Image = etcdConfig.TypedSpec().Image status.TypedSpec().Image = etcdConfig.TypedSpec().Image
status.TypedSpec().ExtraArgs = etcdConfig.TypedSpec().ExtraArgs status.TypedSpec().ExtraArgs = etcdConfig.TypedSpec().ExtraArgs

View File

@ -5,7 +5,6 @@
package etcd_test package etcd_test
import ( import (
"net/netip"
"testing" "testing"
"time" "time"
@ -13,10 +12,12 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"inet.af/netaddr"
"github.com/talos-systems/talos/internal/app/machined/pkg/controllers/ctest" "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/ctest"
etcdctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/etcd" etcdctrl "github.com/talos-systems/talos/internal/app/machined/pkg/controllers/etcd"
"github.com/talos-systems/talos/pkg/machinery/resources/etcd" "github.com/talos-systems/talos/pkg/machinery/resources/etcd"
"github.com/talos-systems/talos/pkg/machinery/resources/k8s"
"github.com/talos-systems/talos/pkg/machinery/resources/network" "github.com/talos-systems/talos/pkg/machinery/resources/network"
) )
@ -35,22 +36,112 @@ type SpecSuite struct {
} }
func (suite *SpecSuite) TestReconcile() { func (suite *SpecSuite) TestReconcile() {
etcdConfig := etcd.NewConfig(etcd.NamespaceName, etcd.ConfigID)
*etcdConfig.TypedSpec() = etcd.ConfigSpec{
ValidSubnets: []string{"0.0.0.0/0", "::/0"},
Image: "foo/bar:v1.0.0",
ExtraArgs: map[string]string{
"arg": "value",
},
}
suite.Require().NoError(suite.State().Create(suite.Ctx(), etcdConfig))
hostnameStatus := network.NewHostnameStatus(network.NamespaceName, network.HostnameID) hostnameStatus := network.NewHostnameStatus(network.NamespaceName, network.HostnameID)
hostnameStatus.TypedSpec().Hostname = "worker1" hostnameStatus.TypedSpec().Hostname = "worker1"
hostnameStatus.TypedSpec().Domainname = "some.domain" hostnameStatus.TypedSpec().Domainname = "some.domain"
suite.Require().NoError(suite.State().Create(suite.Ctx(), hostnameStatus)) suite.Require().NoError(suite.State().Create(suite.Ctx(), hostnameStatus))
addresses := network.NewNodeAddress(
network.NamespaceName,
network.FilteredNodeAddressID(network.NodeAddressRoutedID, k8s.NodeAddressFilterNoK8s),
)
addresses.TypedSpec().Addresses = []netaddr.IPPrefix{
netaddr.MustParseIPPrefix("10.0.0.5/24"),
netaddr.MustParseIPPrefix("192.168.1.1/24"),
netaddr.MustParseIPPrefix("2001:0db8:85a3:0000:0000:8a2e:0370:7334/64"),
netaddr.MustParseIPPrefix("2002:0db8:85a3:0000:0000:8a2e:0370:7335/64"),
}
suite.Require().NoError(suite.State().Create(suite.Ctx(), addresses))
for _, tt := range []struct {
name string
cfg etcd.ConfigSpec
expected etcd.SpecSpec
}{
{
name: "defaults",
cfg: etcd.ConfigSpec{
Image: "foo/bar:v1.0.0",
ExtraArgs: map[string]string{
"arg": "value",
},
},
expected: etcd.SpecSpec{
Name: "worker1",
Image: "foo/bar:v1.0.0",
ExtraArgs: map[string]string{
"arg": "value",
},
AdvertisedAddresses: []netaddr.IP{
netaddr.MustParseIP("10.0.0.5"),
},
ListenPeerAddresses: []netaddr.IP{
netaddr.IPv6Unspecified(),
},
ListenClientAddresses: []netaddr.IP{
netaddr.IPv6Unspecified(),
},
},
},
{
name: "only advertised",
cfg: etcd.ConfigSpec{
Image: "foo/bar:v1.0.0",
AdvertiseValidSubnets: []string{
"192.168.0.0/16",
},
},
expected: etcd.SpecSpec{
Name: "worker1",
Image: "foo/bar:v1.0.0",
AdvertisedAddresses: []netaddr.IP{
netaddr.MustParseIP("192.168.1.1"),
},
ListenPeerAddresses: []netaddr.IP{
netaddr.IPv6Unspecified(),
},
ListenClientAddresses: []netaddr.IP{
netaddr.IPv6Unspecified(),
},
},
},
{
name: "advertised and listen",
cfg: etcd.ConfigSpec{
Image: "foo/bar:v1.0.0",
AdvertiseValidSubnets: []string{
"192.168.0.0/16",
"2001::/16",
},
ListenValidSubnets: []string{
"192.168.0.0/16",
},
},
expected: etcd.SpecSpec{
Name: "worker1",
Image: "foo/bar:v1.0.0",
AdvertisedAddresses: []netaddr.IP{
netaddr.MustParseIP("192.168.1.1"),
netaddr.MustParseIP("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
},
ListenPeerAddresses: []netaddr.IP{
netaddr.MustParseIP("192.168.1.1"),
},
ListenClientAddresses: []netaddr.IP{
netaddr.MustParseIP("::1"),
netaddr.MustParseIP("192.168.1.1"),
},
},
},
} {
suite.Run(tt.name, func() {
etcdConfig := etcd.NewConfig(etcd.NamespaceName, etcd.ConfigID)
*etcdConfig.TypedSpec() = tt.cfg
suite.Require().NoError(suite.State().Create(suite.Ctx(), etcdConfig))
suite.AssertWithin(3*time.Second, 100*time.Millisecond, ctest.WrapRetry(func(assert *assert.Assertions, require *require.Assertions) { suite.AssertWithin(3*time.Second, 100*time.Millisecond, ctest.WrapRetry(func(assert *assert.Assertions, require *require.Assertions) {
etcdSpec, err := safe.StateGet[*etcd.Spec](suite.Ctx(), suite.State(), etcd.NewSpec(etcd.NamespaceName, etcd.SpecID).Metadata()) etcdSpec, err := safe.StateGet[*etcd.Spec](suite.Ctx(), suite.State(), etcd.NewSpec(etcd.NamespaceName, etcd.SpecID).Metadata())
if err != nil { if err != nil {
@ -59,9 +150,10 @@ func (suite *SpecSuite) TestReconcile() {
return return
} }
assert.Equal("foo/bar:v1.0.0", etcdSpec.TypedSpec().Image) assert.Equal(tt.expected, *etcdSpec.TypedSpec())
assert.Equal(map[string]string{"arg": "value"}, etcdSpec.TypedSpec().ExtraArgs)
assert.NotEqual(netip.Addr{}, etcdSpec.TypedSpec().AdvertisedAddress)
assert.True(etcdSpec.TypedSpec().ListenAddress.IsUnspecified())
})) }))
suite.Require().NoError(suite.State().Destroy(suite.Ctx(), etcdConfig.Metadata()))
})
}
} }

View File

@ -26,6 +26,7 @@ import (
"github.com/talos-systems/go-retry/retry" "github.com/talos-systems/go-retry/retry"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
snapshot "go.etcd.io/etcd/etcdutl/v3/snapshot" snapshot "go.etcd.io/etcd/etcdutl/v3/snapshot"
"inet.af/netaddr"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader" "github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/bootloader"
@ -45,6 +46,7 @@ import (
machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine" machineapi "github.com/talos-systems/talos/pkg/machinery/api/machine"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine" "github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1/machine"
"github.com/talos-systems/talos/pkg/machinery/constants" "github.com/talos-systems/talos/pkg/machinery/constants"
"github.com/talos-systems/talos/pkg/machinery/generic/slices"
"github.com/talos-systems/talos/pkg/machinery/nethelpers" "github.com/talos-systems/talos/pkg/machinery/nethelpers"
etcdresource "github.com/talos-systems/talos/pkg/machinery/resources/etcd" etcdresource "github.com/talos-systems/talos/pkg/machinery/resources/etcd"
"github.com/talos-systems/talos/pkg/machinery/resources/k8s" "github.com/talos-systems/talos/pkg/machinery/resources/k8s"
@ -300,7 +302,7 @@ func addMember(ctx context.Context, r runtime.Runtime, addrs []string, name stri
return list, add.Member.ID, nil return list, add.Member.ID, nil
} }
func buildInitialCluster(ctx context.Context, r runtime.Runtime, name, ip string) (initial string, learnerMemberID uint64, err error) { func buildInitialCluster(ctx context.Context, r runtime.Runtime, name string, peerAddrs []string) (initial string, learnerMemberID uint64, err error) {
var ( var (
id uint64 id uint64
lastNag time.Time lastNag time.Time
@ -311,10 +313,7 @@ func buildInitialCluster(ctx context.Context, r runtime.Runtime, name, ip string
retry.WithJitter(time.Second), retry.WithJitter(time.Second),
retry.WithErrorLogging(true), retry.WithErrorLogging(true),
).RetryWithContext(ctx, func(ctx context.Context) error { ).RetryWithContext(ctx, func(ctx context.Context) error {
var ( var resp *clientv3.MemberListResponse
peerAddrs = []string{"https://" + nethelpers.JoinHostPort(ip, +constants.EtcdPeerPort)}
resp *clientv3.MemberListResponse
)
if time.Since(lastNag) > 30*time.Second { if time.Since(lastNag) > 30*time.Second {
lastNag = time.Now() lastNag = time.Now()
@ -396,8 +395,8 @@ func (e *Etcd) argsForInit(ctx context.Context, r runtime.Runtime, spec *etcdres
"auto-tls": "false", "auto-tls": "false",
"peer-auto-tls": "false", "peer-auto-tls": "false",
"data-dir": constants.EtcdDataPath, "data-dir": constants.EtcdDataPath,
"listen-peer-urls": "https://" + nethelpers.JoinHostPort(spec.ListenAddress.String(), constants.EtcdPeerPort), "listen-peer-urls": formatEtcdURLs(spec.ListenPeerAddresses, constants.EtcdPeerPort),
"listen-client-urls": "https://" + nethelpers.JoinHostPort(spec.ListenAddress.String(), constants.EtcdClientPort), "listen-client-urls": formatEtcdURLs(spec.ListenClientAddresses, constants.EtcdClientPort),
"client-cert-auth": "true", "client-cert-auth": "true",
"cert-file": constants.EtcdCert, "cert-file": constants.EtcdCert,
"key-file": constants.EtcdKey, "key-file": constants.EtcdKey,
@ -427,12 +426,12 @@ func (e *Etcd) argsForInit(ctx context.Context, r runtime.Runtime, spec *etcdres
} }
if ok { if ok {
initialCluster := fmt.Sprintf("%s=https://%s", spec.Name, nethelpers.JoinHostPort(spec.AdvertisedAddress.String(), constants.EtcdPeerPort)) initialCluster := fmt.Sprintf("%s=%s", spec.Name, formatEtcdURLs(spec.AdvertisedAddresses, constants.EtcdPeerPort))
if upgraded { if upgraded {
denyListArgs.Set("initial-cluster-state", "existing") denyListArgs.Set("initial-cluster-state", "existing")
initialCluster, e.learnerMemberID, err = buildInitialCluster(ctx, r, spec.Name, spec.AdvertisedAddress.String()) initialCluster, e.learnerMemberID, err = buildInitialCluster(ctx, r, spec.Name, getEtcdURLs(spec.AdvertisedAddresses, constants.EtcdPeerPort))
if err != nil { if err != nil {
return err return err
} }
@ -446,13 +445,13 @@ func (e *Etcd) argsForInit(ctx context.Context, r runtime.Runtime, spec *etcdres
if !extraArgs.Contains("initial-advertise-peer-urls") { if !extraArgs.Contains("initial-advertise-peer-urls") {
denyListArgs.Set("initial-advertise-peer-urls", denyListArgs.Set("initial-advertise-peer-urls",
fmt.Sprintf("https://%s", nethelpers.JoinHostPort(spec.AdvertisedAddress.String(), constants.EtcdPeerPort)), formatEtcdURLs(spec.AdvertisedAddresses, constants.EtcdPeerPort),
) )
} }
if !extraArgs.Contains("advertise-client-urls") { if !extraArgs.Contains("advertise-client-urls") {
denyListArgs.Set("advertise-client-urls", denyListArgs.Set("advertise-client-urls",
fmt.Sprintf("https://%s", nethelpers.JoinHostPort(spec.AdvertisedAddress.String(), constants.EtcdClientPort)), formatEtcdURLs(spec.AdvertisedAddresses, constants.EtcdClientPort),
) )
} }
@ -472,8 +471,8 @@ func (e *Etcd) argsForControlPlane(ctx context.Context, r runtime.Runtime, spec
"auto-tls": "false", "auto-tls": "false",
"peer-auto-tls": "false", "peer-auto-tls": "false",
"data-dir": constants.EtcdDataPath, "data-dir": constants.EtcdDataPath,
"listen-peer-urls": "https://" + nethelpers.JoinHostPort(spec.ListenAddress.String(), constants.EtcdPeerPort), "listen-peer-urls": formatEtcdURLs(spec.ListenPeerAddresses, constants.EtcdPeerPort),
"listen-client-urls": "https://" + nethelpers.JoinHostPort(spec.ListenAddress.String(), constants.EtcdClientPort), "listen-client-urls": formatEtcdURLs(spec.ListenClientAddresses, constants.EtcdClientPort),
"client-cert-auth": "true", "client-cert-auth": "true",
"cert-file": constants.EtcdCert, "cert-file": constants.EtcdCert,
"key-file": constants.EtcdKey, "key-file": constants.EtcdKey,
@ -515,9 +514,9 @@ func (e *Etcd) argsForControlPlane(ctx context.Context, r runtime.Runtime, spec
var initialCluster string var initialCluster string
if e.Bootstrap { if e.Bootstrap {
initialCluster = fmt.Sprintf("%s=https://%s", spec.Name, nethelpers.JoinHostPort(spec.AdvertisedAddress.String(), constants.EtcdPeerPort)) initialCluster = fmt.Sprintf("%s=%s", spec.Name, formatEtcdURLs(spec.AdvertisedAddresses, constants.EtcdPeerPort))
} else { } else {
initialCluster, e.learnerMemberID, err = buildInitialCluster(ctx, r, spec.Name, spec.AdvertisedAddress.String()) initialCluster, e.learnerMemberID, err = buildInitialCluster(ctx, r, spec.Name, getEtcdURLs(spec.AdvertisedAddresses, constants.EtcdPeerPort))
if err != nil { if err != nil {
return fmt.Errorf("failed to build initial etcd cluster: %w", err) return fmt.Errorf("failed to build initial etcd cluster: %w", err)
} }
@ -528,14 +527,14 @@ func (e *Etcd) argsForControlPlane(ctx context.Context, r runtime.Runtime, spec
if !extraArgs.Contains("initial-advertise-peer-urls") { if !extraArgs.Contains("initial-advertise-peer-urls") {
denyListArgs.Set("initial-advertise-peer-urls", denyListArgs.Set("initial-advertise-peer-urls",
fmt.Sprintf("https://%s", nethelpers.JoinHostPort(spec.AdvertisedAddress.String(), constants.EtcdPeerPort)), formatEtcdURLs(spec.AdvertisedAddresses, constants.EtcdPeerPort),
) )
} }
} }
if !extraArgs.Contains("advertise-client-urls") { if !extraArgs.Contains("advertise-client-urls") {
denyListArgs.Set("advertise-client-urls", denyListArgs.Set("advertise-client-urls",
fmt.Sprintf("https://%s", nethelpers.JoinHostPort(spec.AdvertisedAddress.String(), constants.EtcdClientPort)), formatEtcdURLs(spec.AdvertisedAddresses, constants.EtcdClientPort),
) )
} }
@ -566,9 +565,9 @@ func (e *Etcd) recoverFromSnapshot(spec *etcdresource.SpecSpec) error {
Name: spec.Name, Name: spec.Name,
OutputDataDir: constants.EtcdDataPath, OutputDataDir: constants.EtcdDataPath,
PeerURLs: []string{"https://" + nethelpers.JoinHostPort(spec.AdvertisedAddress.String(), constants.EtcdPeerPort)}, PeerURLs: getEtcdURLs(spec.AdvertisedAddresses, constants.EtcdPeerPort),
InitialCluster: fmt.Sprintf("%s=https://%s", spec.Name, nethelpers.JoinHostPort(spec.AdvertisedAddress.String(), constants.EtcdPeerPort)), InitialCluster: fmt.Sprintf("%s=%s", spec.Name, formatEtcdURLs(spec.AdvertisedAddresses, constants.EtcdPeerPort)),
SkipHashCheck: e.RecoverSkipHashCheck, SkipHashCheck: e.RecoverSkipHashCheck,
}); err != nil { }); err != nil {
@ -696,3 +695,17 @@ func BootstrapEtcd(ctx context.Context, r runtime.Runtime, req *machineapi.Boots
return nil return nil
} }
func formatEtcdURL(addr netaddr.IP, port int) string {
return fmt.Sprintf("https://%s", nethelpers.JoinHostPort(addr.String(), port))
}
func getEtcdURLs(addrs []netaddr.IP, port int) []string {
return slices.Map(addrs, func(addr netaddr.IP) string {
return formatEtcdURL(addr, port)
})
}
func formatEtcdURLs(addrs []netaddr.IP, port int) string {
return strings.Join(getEtcdURLs(addrs, port), ",")
}

View File

@ -463,7 +463,8 @@ type Etcd interface {
Image() string Image() string
CA() *x509.PEMEncodedCertificateAndKey CA() *x509.PEMEncodedCertificateAndKey
ExtraArgs() map[string]string ExtraArgs() map[string]string
Subnet() string AdvertisedSubnets() []string
ListenSubnets() []string
} }
// Token defines the requirements for a config that pertains to Kubernetes // Token defines the requirements for a config that pertains to Kubernetes

View File

@ -43,7 +43,31 @@ func (e *EtcdConfig) ExtraArgs() map[string]string {
return e.EtcdExtraArgs return e.EtcdExtraArgs
} }
// Subnet implements the config.Etcd interface. // AdvertisedSubnets implements the config.Etcd interface.
func (e *EtcdConfig) Subnet() string { func (e *EtcdConfig) AdvertisedSubnets() []string {
return e.EtcdSubnet if len(e.EtcdAdvertisedSubnets) > 0 {
return e.EtcdAdvertisedSubnets
}
if e.EtcdSubnet != "" {
return []string{e.EtcdSubnet}
}
return nil
}
// ListenSubnets implements the config.Etcd interface.
func (e *EtcdConfig) ListenSubnets() []string {
if len(e.EtcdListenSubnets) > 0 {
return e.EtcdListenSubnets
}
// if advertised subnets are set, use them
if len(e.EtcdAdvertisedSubnets) > 0 {
return e.EtcdAdvertisedSubnets
}
// nothing set, rely on defaults (listen on all interfaces)
return nil
} }

View File

@ -350,7 +350,7 @@ var (
clusterEtcdImageExample = (&EtcdConfig{}).Image() clusterEtcdImageExample = (&EtcdConfig{}).Image()
clusterEtcdSubnetExample = (&EtcdConfig{EtcdSubnet: "10.0.0.0/8"}).Subnet() clusterEtcdAdvertisedSubnetsExample = (&EtcdConfig{EtcdAdvertisedSubnets: []string{"10.0.0.0/8"}}).AdvertisedSubnets()
clusterCoreDNSExample = &CoreDNS{ clusterCoreDNSExample = &CoreDNS{
CoreDNSImage: (&CoreDNS{}).Image(), CoreDNSImage: (&CoreDNS{}).Image(),
@ -1707,12 +1707,32 @@ type EtcdConfig struct {
// "advertise-client-urls": "https://1.2.3.4:2379", // "advertise-client-urls": "https://1.2.3.4:2379",
// } // }
EtcdExtraArgs map[string]string `yaml:"extraArgs,omitempty"` EtcdExtraArgs map[string]string `yaml:"extraArgs,omitempty"`
// docgen:nodoc
//
// Deprecated: use EtcdAdvertistedSubnets
EtcdSubnet string `yaml:"subnet,omitempty"`
// description: | // description: |
// The subnet from which the advertise URL should be. // The `advertisedSubnets` field configures the networks to pick etcd advertised IP from.
//
// IPs can be excluded from the list by using negative match with `!`, e.g `!10.0.0.0/8`.
// Negative subnet matches should be specified last to filter out IPs picked by positive matches.
// If not specified, advertised IP is selected as the first routable address of the node.
// //
// examples: // examples:
// - value: clusterEtcdSubnetExample // - value: clusterEtcdAdvertisedSubnetsExample
EtcdSubnet string `yaml:"subnet,omitempty"` EtcdAdvertisedSubnets []string `yaml:"advertisedSubnets,omitempty"`
// description: |
// The `listenSubnets` field configures the networks for the etcd to listen for peer and client connections.
//
// If `listenSubnets` is not set, but `advertisedSubnets` is set, `listenSubnets` defaults to
// `advertisedSubnets`.
//
// If neither `advertisedSubnets` nor `listenSubnets` is set, `listenSubnets` defaults to listen on all addresses.
//
// IPs can be excluded from the list by using negative match with `!`, e.g `!10.0.0.0/8`.
// Negative subnet matches should be specified last to filter out IPs picked by positive matches.
// If not specified, advertised IP is selected as the first routable address of the node.
EtcdListenSubnets []string `yaml:"listenSubnets,omitempty"`
} }
// ClusterNetworkConfig represents kube networking configuration options. // ClusterNetworkConfig represents kube networking configuration options.

View File

@ -1248,7 +1248,7 @@ func init() {
FieldName: "etcd", FieldName: "etcd",
}, },
} }
EtcdConfigDoc.Fields = make([]encoder.Doc, 4) EtcdConfigDoc.Fields = make([]encoder.Doc, 6)
EtcdConfigDoc.Fields[0].Name = "image" EtcdConfigDoc.Fields[0].Name = "image"
EtcdConfigDoc.Fields[0].Type = "string" EtcdConfigDoc.Fields[0].Type = "string"
EtcdConfigDoc.Fields[0].Note = "" EtcdConfigDoc.Fields[0].Note = ""
@ -1269,13 +1269,18 @@ func init() {
EtcdConfigDoc.Fields[2].Description = "Extra arguments to supply to etcd.\nNote that the following args are not allowed:\n\n- `name`\n- `data-dir`\n- `initial-cluster-state`\n- `listen-peer-urls`\n- `listen-client-urls`\n- `cert-file`\n- `key-file`\n- `trusted-ca-file`\n- `peer-client-cert-auth`\n- `peer-cert-file`\n- `peer-trusted-ca-file`\n- `peer-key-file`" EtcdConfigDoc.Fields[2].Description = "Extra arguments to supply to etcd.\nNote that the following args are not allowed:\n\n- `name`\n- `data-dir`\n- `initial-cluster-state`\n- `listen-peer-urls`\n- `listen-client-urls`\n- `cert-file`\n- `key-file`\n- `trusted-ca-file`\n- `peer-client-cert-auth`\n- `peer-cert-file`\n- `peer-trusted-ca-file`\n- `peer-key-file`"
EtcdConfigDoc.Fields[2].Comments[encoder.LineComment] = "Extra arguments to supply to etcd." EtcdConfigDoc.Fields[2].Comments[encoder.LineComment] = "Extra arguments to supply to etcd."
EtcdConfigDoc.Fields[3].Name = "subnet" EtcdConfigDoc.Fields[4].Name = "advertisedSubnets"
EtcdConfigDoc.Fields[3].Type = "string" EtcdConfigDoc.Fields[4].Type = "[]string"
EtcdConfigDoc.Fields[3].Note = "" EtcdConfigDoc.Fields[4].Note = ""
EtcdConfigDoc.Fields[3].Description = "The subnet from which the advertise URL should be." EtcdConfigDoc.Fields[4].Description = "The `advertisedSubnets` field configures the networks to pick etcd advertised IP from.\n\nIPs can be excluded from the list by using negative match with `!`, e.g `!10.0.0.0/8`.\nNegative subnet matches should be specified last to filter out IPs picked by positive matches.\nIf not specified, advertised IP is selected as the first routable address of the node."
EtcdConfigDoc.Fields[3].Comments[encoder.LineComment] = "The subnet from which the advertise URL should be." EtcdConfigDoc.Fields[4].Comments[encoder.LineComment] = "The `advertisedSubnets` field configures the networks to pick etcd advertised IP from."
EtcdConfigDoc.Fields[3].AddExample("", clusterEtcdSubnetExample) EtcdConfigDoc.Fields[4].AddExample("", clusterEtcdAdvertisedSubnetsExample)
EtcdConfigDoc.Fields[5].Name = "listenSubnets"
EtcdConfigDoc.Fields[5].Type = "[]string"
EtcdConfigDoc.Fields[5].Note = ""
EtcdConfigDoc.Fields[5].Description = "The `listenSubnets` field configures the networks for the etcd to listen for peer and client connections.\n\nIf `listenSubnets` is not set, but `advertisedSubnets` is set, `listenSubnets` defaults to\n`advertisedSubnets`.\n\nIf neither `advertisedSubnets` nor `listenSubnets` is set, `listenSubnets` defaults to listen on all addresses.\n\nIPs can be excluded from the list by using negative match with `!`, e.g `!10.0.0.0/8`.\nNegative subnet matches should be specified last to filter out IPs picked by positive matches.\nIf not specified, advertised IP is selected as the first routable address of the node."
EtcdConfigDoc.Fields[5].Comments[encoder.LineComment] = "The `listenSubnets` field configures the networks for the etcd to listen for peer and client connections."
ClusterNetworkConfigDoc.Type = "ClusterNetworkConfig" ClusterNetworkConfigDoc.Type = "ClusterNetworkConfig"
ClusterNetworkConfigDoc.Comments[encoder.LineComment] = "ClusterNetworkConfig represents kube networking configuration options." ClusterNetworkConfigDoc.Comments[encoder.LineComment] = "ClusterNetworkConfig represents kube networking configuration options."

View File

@ -310,10 +310,8 @@ func (c *ClusterConfig) Validate() error {
result = multierror.Append(result, ecp.Validate()) result = multierror.Append(result, ecp.Validate())
} }
if c.EtcdConfig != nil && c.EtcdConfig.EtcdSubnet != "" { if c.EtcdConfig != nil {
if _, _, err := net.ParseCIDR(c.EtcdConfig.EtcdSubnet); err != nil { result = multierror.Append(result, c.EtcdConfig.Validate())
result = multierror.Append(result, fmt.Errorf("%q is not a valid subnet", c.EtcdConfig.EtcdSubnet))
}
} }
result = multierror.Append(result, c.ClusterInlineManifests.Validate(), c.ClusterDiscoveryConfig.Validate(c)) result = multierror.Append(result, c.ClusterInlineManifests.Validate(), c.ClusterDiscoveryConfig.Validate(c))
@ -793,3 +791,30 @@ func (k *KubeletConfig) Validate() ([]string, error) {
return nil, result.ErrorOrNil() return nil, result.ErrorOrNil()
} }
// Validate kubelet configuration.
func (e *EtcdConfig) Validate() error {
var result *multierror.Error
if e.EtcdSubnet != "" && len(e.EtcdAdvertisedSubnets) > 0 {
result = multierror.Append(result, fmt.Errorf("etcd subnet can't be set when advertised subnets are set"))
}
for _, cidr := range e.AdvertisedSubnets() {
cidr = strings.TrimPrefix(cidr, "!")
if _, err := talosnet.ParseCIDR(cidr); err != nil {
result = multierror.Append(result, fmt.Errorf("etcd advertised subnet is not valid: %q", cidr))
}
}
for _, cidr := range e.ListenSubnets() {
cidr = strings.TrimPrefix(cidr, "!")
if _, err := talosnet.ParseCIDR(cidr); err != nil {
result = multierror.Append(result, fmt.Errorf("etcd listen subnet is not valid: %q", cidr))
}
}
return result.ErrorOrNil()
}

View File

@ -961,7 +961,14 @@ func TestValidate(t *testing.T) {
}, },
}, },
EtcdConfig: &v1alpha1.EtcdConfig{ EtcdConfig: &v1alpha1.EtcdConfig{
EtcdSubnet: "10.0.0.0/8", EtcdAdvertisedSubnets: []string{
"10.0.0.0/8",
"!1.1.1.1/32",
},
EtcdListenSubnets: []string{
"10.0.0.0/8",
"1.1.1.1/32",
},
}, },
}, },
}, },
@ -981,11 +988,16 @@ func TestValidate(t *testing.T) {
}, },
}, },
EtcdConfig: &v1alpha1.EtcdConfig{ EtcdConfig: &v1alpha1.EtcdConfig{
EtcdSubnet: "10.0.0.0", EtcdAdvertisedSubnets: []string{
"1234:",
},
EtcdListenSubnets: []string{
"10",
}, },
}, },
}, },
expectedError: "1 error occurred:\n\t* \"10.0.0.0\" is not a valid subnet\n\n", },
expectedError: "2 errors occurred:\n\t* etcd advertised subnet is not valid: \"1234:\"\n\t* etcd listen subnet is not valid: \"10\"\n\n",
}, },
{ {
name: "GoodKubeletSubnet", name: "GoodKubeletSubnet",

View File

@ -859,6 +859,16 @@ func (in *EtcdConfig) DeepCopyInto(out *EtcdConfig) {
(*out)[key] = val (*out)[key] = val
} }
} }
if in.EtcdAdvertisedSubnets != nil {
in, out := &in.EtcdAdvertisedSubnets, &out.EtcdAdvertisedSubnets
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.EtcdListenSubnets != nil {
in, out := &in.EtcdListenSubnets, &out.EtcdListenSubnets
*out = make([]string, len(*in))
copy(*out, *in)
}
return return
} }

View File

@ -26,8 +26,12 @@ type Config = typed.Resource[ConfigSpec, ConfigRD]
// //
//gotagsrewrite:gen //gotagsrewrite:gen
type ConfigSpec struct { type ConfigSpec struct {
ValidSubnets []string `yaml:"validSubnets,omitempty" protobuf:"1"` AdvertiseValidSubnets []string `yaml:"advertiseValidSubnets,omitempty" protobuf:"1"`
ExcludeSubnets []string `yaml:"excludeSubnets" protobuf:"2"` AdvertiseExcludeSubnets []string `yaml:"advertiseExcludeSubnets" protobuf:"2"`
ListenValidSubnets []string `yaml:"listenValidSubnets,omitempty" protobuf:"5"`
ListenExcludeSubnets []string `yaml:"listenExcludeSubnets" protobuf:"6"`
Image string `yaml:"image" protobuf:"3"` Image string `yaml:"image" protobuf:"3"`
ExtraArgs map[string]string `yaml:"extraArgs" protobuf:"4"` ExtraArgs map[string]string `yaml:"extraArgs" protobuf:"4"`
} }

View File

@ -6,16 +6,28 @@
package etcd package etcd
import (
"inet.af/netaddr"
)
// DeepCopy generates a deep copy of ConfigSpec. // DeepCopy generates a deep copy of ConfigSpec.
func (o ConfigSpec) DeepCopy() ConfigSpec { func (o ConfigSpec) DeepCopy() ConfigSpec {
var cp ConfigSpec = o var cp ConfigSpec = o
if o.ValidSubnets != nil { if o.AdvertiseValidSubnets != nil {
cp.ValidSubnets = make([]string, len(o.ValidSubnets)) cp.AdvertiseValidSubnets = make([]string, len(o.AdvertiseValidSubnets))
copy(cp.ValidSubnets, o.ValidSubnets) copy(cp.AdvertiseValidSubnets, o.AdvertiseValidSubnets)
} }
if o.ExcludeSubnets != nil { if o.AdvertiseExcludeSubnets != nil {
cp.ExcludeSubnets = make([]string, len(o.ExcludeSubnets)) cp.AdvertiseExcludeSubnets = make([]string, len(o.AdvertiseExcludeSubnets))
copy(cp.ExcludeSubnets, o.ExcludeSubnets) copy(cp.AdvertiseExcludeSubnets, o.AdvertiseExcludeSubnets)
}
if o.ListenValidSubnets != nil {
cp.ListenValidSubnets = make([]string, len(o.ListenValidSubnets))
copy(cp.ListenValidSubnets, o.ListenValidSubnets)
}
if o.ListenExcludeSubnets != nil {
cp.ListenExcludeSubnets = make([]string, len(o.ListenExcludeSubnets))
copy(cp.ListenExcludeSubnets, o.ListenExcludeSubnets)
} }
if o.ExtraArgs != nil { if o.ExtraArgs != nil {
cp.ExtraArgs = make(map[string]string, len(o.ExtraArgs)) cp.ExtraArgs = make(map[string]string, len(o.ExtraArgs))
@ -35,6 +47,18 @@ func (o PKIStatusSpec) DeepCopy() PKIStatusSpec {
// DeepCopy generates a deep copy of SpecSpec. // DeepCopy generates a deep copy of SpecSpec.
func (o SpecSpec) DeepCopy() SpecSpec { func (o SpecSpec) DeepCopy() SpecSpec {
var cp SpecSpec = o var cp SpecSpec = o
if o.AdvertisedAddresses != nil {
cp.AdvertisedAddresses = make([]netaddr.IP, len(o.AdvertisedAddresses))
copy(cp.AdvertisedAddresses, o.AdvertisedAddresses)
}
if o.ListenPeerAddresses != nil {
cp.ListenPeerAddresses = make([]netaddr.IP, len(o.ListenPeerAddresses))
copy(cp.ListenPeerAddresses, o.ListenPeerAddresses)
}
if o.ListenClientAddresses != nil {
cp.ListenClientAddresses = make([]netaddr.IP, len(o.ListenClientAddresses))
copy(cp.ListenClientAddresses, o.ListenClientAddresses)
}
if o.ExtraArgs != nil { if o.ExtraArgs != nil {
cp.ExtraArgs = make(map[string]string, len(o.ExtraArgs)) cp.ExtraArgs = make(map[string]string, len(o.ExtraArgs))
for k2, v2 := range o.ExtraArgs { for k2, v2 := range o.ExtraArgs {

View File

@ -5,12 +5,11 @@
package etcd package etcd
import ( import (
"net/netip"
"github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/resource"
"github.com/cosi-project/runtime/pkg/resource/meta" "github.com/cosi-project/runtime/pkg/resource/meta"
"github.com/cosi-project/runtime/pkg/resource/protobuf" "github.com/cosi-project/runtime/pkg/resource/protobuf"
"github.com/cosi-project/runtime/pkg/resource/typed" "github.com/cosi-project/runtime/pkg/resource/typed"
"inet.af/netaddr"
"github.com/talos-systems/talos/pkg/machinery/proto" "github.com/talos-systems/talos/pkg/machinery/proto"
) )
@ -29,8 +28,9 @@ type Spec = typed.Resource[SpecSpec, SpecRD]
//gotagsrewrite:gen //gotagsrewrite:gen
type SpecSpec struct { type SpecSpec struct {
Name string `yaml:"name" protobuf:"1"` Name string `yaml:"name" protobuf:"1"`
AdvertisedAddress netip.Addr `yaml:"advertisedAddress" protobuf:"2"` AdvertisedAddresses []netaddr.IP `yaml:"advertisedAddresses" protobuf:"2"`
ListenAddress netip.Addr `yaml:"listenAddress" protobuf:"5"` ListenPeerAddresses []netaddr.IP `yaml:"listenPeerAddresses" protobuf:"5"`
ListenClientAddresses []netaddr.IP `yaml:"listenClientAddresses" protobuf:"6"`
Image string `yaml:"image" protobuf:"3"` Image string `yaml:"image" protobuf:"3"`
ExtraArgs map[string]string `yaml:"extraArgs" protobuf:"4"` ExtraArgs map[string]string `yaml:"extraArgs" protobuf:"4"`
} }
@ -58,8 +58,16 @@ func (SpecRD) ResourceDefinition(resource.Metadata, SpecSpec) meta.ResourceDefin
JSONPath: "{.name}", JSONPath: "{.name}",
}, },
{ {
Name: "AdvertisedAddress", Name: "AdvertisedAddresses",
JSONPath: "{.advertisedAddress}", JSONPath: "{.advertisedAddresses}",
},
{
Name: "ListenPeerAddresses",
JSONPath: "{.listenPeerAddresses}",
},
{
Name: "ListenClientAddresses",
JSONPath: "{.listenClientAddresses}",
}, },
}, },
} }

View File

@ -581,8 +581,9 @@ etcd:
extraArgs: extraArgs:
election-timeout: "5000" election-timeout: "5000"
# # The subnet from which the advertise URL should be. # # The `advertisedSubnets` field configures the networks to pick etcd advertised IP from.
# subnet: 10.0.0.0/8 # advertisedSubnets:
# - 10.0.0.0/8
{{< /highlight >}}</details> | | {{< /highlight >}}</details> | |
|`coreDNS` |<a href="#coredns">CoreDNS</a> |Core DNS specific configuration options. <details><summary>Show example(s)</summary>{{< highlight yaml >}} |`coreDNS` |<a href="#coredns">CoreDNS</a> |Core DNS specific configuration options. <details><summary>Show example(s)</summary>{{< highlight yaml >}}
coreDNS: coreDNS:
@ -1568,8 +1569,9 @@ ca:
extraArgs: extraArgs:
election-timeout: "5000" election-timeout: "5000"
# # The subnet from which the advertise URL should be. # # The `advertisedSubnets` field configures the networks to pick etcd advertised IP from.
# subnet: 10.0.0.0/8 # advertisedSubnets:
# - 10.0.0.0/8
{{< /highlight >}} {{< /highlight >}}
@ -1584,9 +1586,11 @@ ca:
key: LS0tIEVYQU1QTEUgS0VZIC0tLQ== key: LS0tIEVYQU1QTEUgS0VZIC0tLQ==
{{< /highlight >}}</details> | | {{< /highlight >}}</details> | |
|`extraArgs` |map[string]string |<details><summary>Extra arguments to supply to etcd.</summary>Note that the following args are not allowed:<br /><br />- `name`<br />- `data-dir`<br />- `initial-cluster-state`<br />- `listen-peer-urls`<br />- `listen-client-urls`<br />- `cert-file`<br />- `key-file`<br />- `trusted-ca-file`<br />- `peer-client-cert-auth`<br />- `peer-cert-file`<br />- `peer-trusted-ca-file`<br />- `peer-key-file`</details> | | |`extraArgs` |map[string]string |<details><summary>Extra arguments to supply to etcd.</summary>Note that the following args are not allowed:<br /><br />- `name`<br />- `data-dir`<br />- `initial-cluster-state`<br />- `listen-peer-urls`<br />- `listen-client-urls`<br />- `cert-file`<br />- `key-file`<br />- `trusted-ca-file`<br />- `peer-client-cert-auth`<br />- `peer-cert-file`<br />- `peer-trusted-ca-file`<br />- `peer-key-file`</details> | |
|`subnet` |string |The subnet from which the advertise URL should be. <details><summary>Show example(s)</summary>{{< highlight yaml >}} |`advertisedSubnets` |[]string |<details><summary>The `advertisedSubnets` field configures the networks to pick etcd advertised IP from.</summary><br />IPs can be excluded from the list by using negative match with `!`, e.g `!10.0.0.0/8`.<br />Negative subnet matches should be specified last to filter out IPs picked by positive matches.<br />If not specified, advertised IP is selected as the first routable address of the node.</details> <details><summary>Show example(s)</summary>{{< highlight yaml >}}
subnet: 10.0.0.0/8 advertisedSubnets:
- 10.0.0.0/8
{{< /highlight >}}</details> | | {{< /highlight >}}</details> | |
|`listenSubnets` |[]string |<details><summary>The `listenSubnets` field configures the networks for the etcd to listen for peer and client connections.</summary><br />If `listenSubnets` is not set, but `advertisedSubnets` is set, `listenSubnets` defaults to<br />`advertisedSubnets`.<br /><br />If neither `advertisedSubnets` nor `listenSubnets` is set, `listenSubnets` defaults to listen on all addresses.<br /><br />IPs can be excluded from the list by using negative match with `!`, e.g `!10.0.0.0/8`.<br />Negative subnet matches should be specified last to filter out IPs picked by positive matches.<br />If not specified, advertised IP is selected as the first routable address of the node.</details> | |