mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
fix: add hostname to endpoints
Populate endpoint coming from the Kubernetes controlplane endpoint with the hostname (if the endpoint is a hostname). This should improve cases when hostname is used for the endpoint in terms of SNI, proper resolving of DNS if it's dynamic. See https://github.com/siderolabs/talos/pull/12556#issuecomment-3755862314 Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com> (cherry picked from commit 96e604874b17e7aa8b62bfb25737f349e539bc5a)
This commit is contained in:
parent
624f9b52ab
commit
b8f8245253
@ -93,9 +93,10 @@ message ControllerManagerConfigSpec {
|
||||
Resources resources = 9;
|
||||
}
|
||||
|
||||
// EndpointSpec describes status of rendered secrets.
|
||||
// EndpointSpec describes a list of endpoints to connect to.
|
||||
message EndpointSpec {
|
||||
repeated common.NetIP addresses = 1;
|
||||
repeated string hosts = 2;
|
||||
}
|
||||
|
||||
// ExtraManifest defines a single extra manifest to download.
|
||||
|
||||
@ -75,6 +75,7 @@ func (ctrl *StaticEndpointController) Run(ctx context.Context, r controller.Runt
|
||||
var (
|
||||
resolver net.Resolver
|
||||
addrs []netip.Addr
|
||||
hosts []string
|
||||
)
|
||||
|
||||
addrs, err = resolver.LookupNetIP(ctx, "ip", cpHostname)
|
||||
@ -84,8 +85,13 @@ func (ctrl *StaticEndpointController) Run(ctx context.Context, r controller.Runt
|
||||
|
||||
addrs = xslices.Map(addrs, netip.Addr.Unmap)
|
||||
|
||||
if len(addrs) != 1 || addrs[0].String() != cpHostname {
|
||||
hosts = []string{cpHostname}
|
||||
}
|
||||
|
||||
if err = safe.WriterModify(ctx, r, k8s.NewEndpoint(k8s.ControlPlaneNamespaceName, k8s.ControlPlaneKubernetesEndpointsID), func(endpoint *k8s.Endpoint) error {
|
||||
endpoint.TypedSpec().Addresses = addrs
|
||||
endpoint.TypedSpec().Hosts = hosts
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
|
||||
@ -51,6 +51,7 @@ func (suite *StaticEndpointControllerSuite) TestReconcile() {
|
||||
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ControlPlaneKubernetesEndpointsID},
|
||||
func(endpoint *k8s.Endpoint, assert *assert.Assertions) {
|
||||
assert.Equal([]netip.Addr{netip.MustParseAddr("2001:db8::1")}, endpoint.TypedSpec().Addresses)
|
||||
assert.Empty(endpoint.TypedSpec().Hosts)
|
||||
})
|
||||
|
||||
suite.Require().NoError(suite.State().Destroy(suite.Ctx(), cfg.Metadata()))
|
||||
@ -58,6 +59,36 @@ func (suite *StaticEndpointControllerSuite) TestReconcile() {
|
||||
rtestutils.AssertNoResource[*k8s.Endpoint](suite.Ctx(), suite.T(), suite.State(), k8s.ControlPlaneKubernetesEndpointsID)
|
||||
}
|
||||
|
||||
func (suite *StaticEndpointControllerSuite) TestReconcileHostname() {
|
||||
u, err := url.Parse("https://localhost:6443/")
|
||||
suite.Require().NoError(err)
|
||||
|
||||
cfg := config.NewMachineConfig(
|
||||
container.NewV1Alpha1(
|
||||
&v1alpha1.Config{
|
||||
ConfigVersion: "v1alpha1",
|
||||
MachineConfig: &v1alpha1.MachineConfig{},
|
||||
ClusterConfig: &v1alpha1.ClusterConfig{
|
||||
ControlPlane: &v1alpha1.ControlPlaneConfig{
|
||||
Endpoint: &v1alpha1.Endpoint{
|
||||
URL: u,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
suite.Require().NoError(suite.State().Create(suite.Ctx(), cfg))
|
||||
|
||||
rtestutils.AssertResources(suite.Ctx(), suite.T(), suite.State(), []resource.ID{k8s.ControlPlaneKubernetesEndpointsID},
|
||||
func(endpoint *k8s.Endpoint, assert *assert.Assertions) {
|
||||
// localhost might resolve to ::1 as well, check only for 127.0.0.1
|
||||
assert.Contains(endpoint.TypedSpec().Addresses, netip.MustParseAddr("127.0.0.1"))
|
||||
assert.Equal([]string{"localhost"}, endpoint.TypedSpec().Hosts)
|
||||
})
|
||||
}
|
||||
|
||||
func TestStaticEndpointControllerSuite(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@ -108,7 +108,7 @@ func (ctrl *EndpointController) Run(ctx context.Context, r controller.Runtime, l
|
||||
endpointAddrs = endpointAddrs.Merge(endpointResource)
|
||||
}
|
||||
|
||||
if len(endpointAddrs) == 0 {
|
||||
if endpointAddrs.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -212,20 +212,20 @@ func (ctrl *EndpointController) ensureTalosEndpointSlices(ctx context.Context, l
|
||||
addrsIPv6 k8s.EndpointList
|
||||
)
|
||||
|
||||
for _, addr := range endpointAddrs {
|
||||
for _, addr := range endpointAddrs.Addresses {
|
||||
switch {
|
||||
case addr.Is4():
|
||||
addrsIPv4 = append(addrsIPv4, addr)
|
||||
addrsIPv4.Addresses = append(addrsIPv4.Addresses, addr)
|
||||
|
||||
case addr.Is6():
|
||||
addrsIPv6 = append(addrsIPv6, addr)
|
||||
addrsIPv6.Addresses = append(addrsIPv6.Addresses, addr)
|
||||
|
||||
default:
|
||||
// ignore other address types
|
||||
}
|
||||
}
|
||||
|
||||
if len(addrsIPv4) == 0 {
|
||||
if len(addrsIPv4.Addresses) == 0 {
|
||||
if err := ctrl.deleteTalosEndpointSlicesTyped(ctx, logger, client, discoveryv1.AddressTypeIPv4); err != nil {
|
||||
return fmt.Errorf("error deleting Talos API endpoint slices for IPv4: %w", err)
|
||||
}
|
||||
@ -235,7 +235,7 @@ func (ctrl *EndpointController) ensureTalosEndpointSlices(ctx context.Context, l
|
||||
}
|
||||
}
|
||||
|
||||
if len(addrsIPv6) == 0 {
|
||||
if len(addrsIPv6.Addresses) == 0 {
|
||||
if err := ctrl.deleteTalosEndpointSlicesTyped(ctx, logger, client, discoveryv1.AddressTypeIPv6); err != nil {
|
||||
return fmt.Errorf("error deleting Talos API endpoint slices for IPv6: %w", err)
|
||||
}
|
||||
@ -306,7 +306,7 @@ func (ctrl *EndpointController) ensureTalosEndpointSlicesTyped(
|
||||
},
|
||||
}
|
||||
|
||||
for _, addr := range endpointAddrs {
|
||||
for _, addr := range endpointAddrs.Addresses {
|
||||
newEndpointSlice.Endpoints = append(
|
||||
newEndpointSlice.Endpoints,
|
||||
discoveryv1.Endpoint{
|
||||
|
||||
@ -257,7 +257,7 @@ func (ctrl *APIController) reconcile(ctx context.Context, r controller.Runtime,
|
||||
endpointAddrs = endpointAddrs.Merge(res.(*k8s.Endpoint))
|
||||
}
|
||||
|
||||
if len(endpointAddrs) == 0 {
|
||||
if endpointAddrs.IsEmpty() {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ func GetEndpoints(ctx context.Context, resources state.State) ([]string, error)
|
||||
endpointAddrs = endpointAddrs.Merge(res)
|
||||
}
|
||||
|
||||
if len(endpointAddrs) == 0 {
|
||||
if endpointAddrs.IsEmpty() {
|
||||
return nil, errors.New("no controlplane endpoints discovered yet")
|
||||
}
|
||||
|
||||
|
||||
@ -743,10 +743,11 @@ func (x *ControllerManagerConfigSpec) GetResources() *Resources {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EndpointSpec describes status of rendered secrets.
|
||||
// EndpointSpec describes a list of endpoints to connect to.
|
||||
type EndpointSpec struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Addresses []*common.NetIP `protobuf:"bytes,1,rep,name=addresses,proto3" json:"addresses,omitempty"`
|
||||
Hosts []string `protobuf:"bytes,2,rep,name=hosts,proto3" json:"hosts,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -788,6 +789,13 @@ func (x *EndpointSpec) GetAddresses() []*common.NetIP {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *EndpointSpec) GetHosts() []string {
|
||||
if x != nil {
|
||||
return x.Hosts
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExtraManifest defines a single extra manifest to download.
|
||||
type ExtraManifest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
@ -2378,9 +2386,10 @@ const file_resource_definitions_k8s_k8s_proto_rawDesc = "" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\x1aG\n" +
|
||||
"\x19EnvironmentVariablesEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\";\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"Q\n" +
|
||||
"\fEndpointSpec\x12+\n" +
|
||||
"\taddresses\x18\x01 \x03(\v2\r.common.NetIPR\taddresses\"\xa1\x02\n" +
|
||||
"\taddresses\x18\x01 \x03(\v2\r.common.NetIPR\taddresses\x12\x14\n" +
|
||||
"\x05hosts\x18\x02 \x03(\tR\x05hosts\"\xa1\x02\n" +
|
||||
"\rExtraManifest\x12\x12\n" +
|
||||
"\x04name\x18\x01 \x01(\tR\x04name\x12\x10\n" +
|
||||
"\x03url\x18\x02 \x01(\tR\x03url\x12\x1a\n" +
|
||||
|
||||
@ -810,6 +810,15 @@ func (m *EndpointSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
|
||||
i -= len(m.unknownFields)
|
||||
copy(dAtA[i:], m.unknownFields)
|
||||
}
|
||||
if len(m.Hosts) > 0 {
|
||||
for iNdEx := len(m.Hosts) - 1; iNdEx >= 0; iNdEx-- {
|
||||
i -= len(m.Hosts[iNdEx])
|
||||
copy(dAtA[i:], m.Hosts[iNdEx])
|
||||
i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Hosts[iNdEx])))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
}
|
||||
if len(m.Addresses) > 0 {
|
||||
for iNdEx := len(m.Addresses) - 1; iNdEx >= 0; iNdEx-- {
|
||||
if vtmsg, ok := interface{}(m.Addresses[iNdEx]).(interface {
|
||||
@ -2747,6 +2756,12 @@ func (m *EndpointSpec) SizeVT() (n int) {
|
||||
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
|
||||
}
|
||||
}
|
||||
if len(m.Hosts) > 0 {
|
||||
for _, s := range m.Hosts {
|
||||
l = len(s)
|
||||
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
|
||||
}
|
||||
}
|
||||
n += len(m.unknownFields)
|
||||
return n
|
||||
}
|
||||
@ -5731,6 +5746,38 @@ func (m *EndpointSpec) UnmarshalVT(dAtA []byte) error {
|
||||
}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Hosts", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return protohelpers.ErrIntOverflow
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return protohelpers.ErrInvalidLength
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Hosts = append(m.Hosts, string(dAtA[iNdEx:postIndex]))
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
|
||||
|
||||
@ -175,6 +175,10 @@ func (o EndpointSpec) DeepCopy() EndpointSpec {
|
||||
cp.Addresses = make([]netip.Addr, len(o.Addresses))
|
||||
copy(cp.Addresses, o.Addresses)
|
||||
}
|
||||
if o.Hosts != nil {
|
||||
cp.Hosts = make([]string, len(o.Hosts))
|
||||
copy(cp.Hosts, o.Hosts)
|
||||
}
|
||||
return cp
|
||||
}
|
||||
|
||||
|
||||
@ -32,11 +32,12 @@ const ControlPlaneKubernetesEndpointsID = resource.ID("controlplane")
|
||||
// Endpoint resource holds definition of rendered secrets.
|
||||
type Endpoint = typed.Resource[EndpointSpec, EndpointExtension]
|
||||
|
||||
// EndpointSpec describes status of rendered secrets.
|
||||
// EndpointSpec describes a list of endpoints to connect to.
|
||||
//
|
||||
//gotagsrewrite:gen
|
||||
type EndpointSpec struct {
|
||||
Addresses []netip.Addr `yaml:"addresses" protobuf:"1"`
|
||||
Hosts []string `yaml:"hosts" protobuf:"2"`
|
||||
}
|
||||
|
||||
// NewEndpoint initializes the Endpoint resource.
|
||||
@ -61,32 +62,56 @@ func (EndpointExtension) ResourceDefinition() meta.ResourceDefinitionSpec {
|
||||
Name: "Addresses",
|
||||
JSONPath: "{.addresses}",
|
||||
},
|
||||
{
|
||||
Name: "Hosts",
|
||||
JSONPath: "{.hosts}",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// EndpointList is a flattened list of endpoints.
|
||||
type EndpointList []netip.Addr
|
||||
type EndpointList struct {
|
||||
Addresses []netip.Addr
|
||||
Hosts []string
|
||||
}
|
||||
|
||||
// Merge endpoints from multiple Endpoint resources into a single list.
|
||||
func (l EndpointList) Merge(endpoint *Endpoint) EndpointList {
|
||||
for _, ip := range endpoint.TypedSpec().Addresses {
|
||||
idx, _ := slices.BinarySearchFunc(l, ip, func(a netip.Addr, target netip.Addr) int {
|
||||
idx, _ := slices.BinarySearchFunc(l.Addresses, ip, func(a netip.Addr, target netip.Addr) int {
|
||||
return a.Compare(target)
|
||||
})
|
||||
if idx < len(l) && l[idx].Compare(ip) == 0 {
|
||||
if idx < len(l.Addresses) && l.Addresses[idx].Compare(ip) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
l = slices.Insert(l, idx, ip)
|
||||
l.Addresses = slices.Insert(l.Addresses, idx, ip)
|
||||
}
|
||||
|
||||
for _, host := range endpoint.TypedSpec().Hosts {
|
||||
idx, _ := slices.BinarySearch(l.Hosts, host)
|
||||
if idx < len(l.Hosts) && l.Hosts[idx] == host {
|
||||
continue
|
||||
}
|
||||
|
||||
l.Hosts = slices.Insert(l.Hosts, idx, host)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
// IsEmpty checks if the EndpointList is empty.
|
||||
func (l EndpointList) IsEmpty() bool {
|
||||
return len(l.Addresses) == 0 && len(l.Hosts) == 0
|
||||
}
|
||||
|
||||
// Strings returns a slice of formatted endpoints to string.
|
||||
func (l EndpointList) Strings() []string {
|
||||
return xslices.Map(l, netip.Addr.String)
|
||||
return slices.Concat(
|
||||
xslices.Map(l.Addresses, netip.Addr.String),
|
||||
l.Hosts,
|
||||
)
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
@ -35,3 +35,43 @@ func TestEndpointList(t *testing.T) {
|
||||
|
||||
assert.Equal(t, []string{"172.20.0.2", "172.20.0.3", "172.20.0.4"}, l.Strings())
|
||||
}
|
||||
|
||||
func TestEndpointListWithHosts(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var l k8s.EndpointList
|
||||
|
||||
assert.True(t, l.IsEmpty())
|
||||
|
||||
e1 := k8s.NewEndpoint(k8s.ControlPlaneNamespaceName, "1")
|
||||
e1.TypedSpec().Addresses = []netip.Addr{
|
||||
netip.MustParseAddr("172.20.0.2"),
|
||||
}
|
||||
e1.TypedSpec().Hosts = []string{
|
||||
"host1.example.com",
|
||||
"host2.example.com",
|
||||
}
|
||||
|
||||
e2 := k8s.NewEndpoint(k8s.ControlPlaneNamespaceName, "2")
|
||||
e2.TypedSpec().Addresses = []netip.Addr{
|
||||
netip.MustParseAddr("172.20.0.3"),
|
||||
}
|
||||
e2.TypedSpec().Hosts = []string{
|
||||
"host2.example.com",
|
||||
"host3.example.com",
|
||||
}
|
||||
|
||||
l = l.Merge(e1)
|
||||
l = l.Merge(e2)
|
||||
|
||||
assert.Equal(t,
|
||||
[]string{
|
||||
"172.20.0.2",
|
||||
"172.20.0.3",
|
||||
"host1.example.com",
|
||||
"host2.example.com",
|
||||
"host3.example.com",
|
||||
},
|
||||
l.Strings(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -7067,12 +7067,13 @@ ControllerManagerConfigSpec is configuration for kube-controller-manager.
|
||||
<a name="talos.resource.definitions.k8s.EndpointSpec"></a>
|
||||
|
||||
### EndpointSpec
|
||||
EndpointSpec describes status of rendered secrets.
|
||||
EndpointSpec describes a list of endpoints to connect to.
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| addresses | [common.NetIP](#common.NetIP) | repeated | |
|
||||
| hosts | [string](#string) | repeated | |
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user