mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-10 15:11:15 +02:00
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:
parent
4c3485ae3f
commit
dce923f747
@ -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()
|
||||||
|
@ -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()))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()))
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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), ",")
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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."
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -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",
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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}",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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> | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user