Skip to content

Commit

Permalink
Add IP Stack to DNSRecord resources (gardener#9289)
Browse files Browse the repository at this point in the history
* Add ip stack annotation to dns record resources

* Add utility functions for determining ip stack from shoot/seed

* Add ip stack to seed ingress dns record

* Add ip stack to shoot dns records (external/internal domain and ingress)

* Address review feedback
  • Loading branch information
ScheererJ authored Mar 1, 2024
1 parent 12e8f78 commit bdffece
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 0 deletions.
7 changes: 7 additions & 0 deletions pkg/component/extensions/dnsrecord/dnsrecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/gardener/gardener/pkg/component"
"github.com/gardener/gardener/pkg/controllerutils"
"github.com/gardener/gardener/pkg/extensions"
gardenerutils "github.com/gardener/gardener/pkg/utils/gardener"
)

const (
Expand Down Expand Up @@ -85,6 +86,8 @@ type Values struct {
Values []string
// TTL is the time to live in seconds of the DNSRecord.
TTL *int64
// IPStack is the indication of the IP stack used for the DNSRecord. It can be ipv4, ipv6 or dual-stack.
IPStack string
}

// New creates a new instance that implements component.DeployMigrateWaiter.
Expand Down Expand Up @@ -151,6 +154,10 @@ func (d *dnsRecord) deploy(ctx context.Context, operation string) (extensionsv1a
metav1.SetMetaDataAnnotation(&d.dnsRecord.ObjectMeta, v1beta1constants.GardenerTimestamp, TimeNow().UTC().Format(time.RFC3339Nano))
}

if d.values.IPStack != "" {
metav1.SetMetaDataAnnotation(&d.dnsRecord.ObjectMeta, gardenerutils.AnnotationKeyIPStack, d.values.IPStack)
}

d.dnsRecord.Spec = extensionsv1alpha1.DNSRecordSpec{
DefaultSpec: extensionsv1alpha1.DefaultSpec{
Type: d.values.Type,
Expand Down
27 changes: 27 additions & 0 deletions pkg/component/extensions/dnsrecord/dnsrecord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,33 @@ var _ = Describe("DNSRecord", func() {
Expect(deployedDNS).To(DeepEqual(expectedDNSRecord))
})

It("should deploy the DNSRecord resource with ip stack annotation", func() {
values.IPStack = "ipv5"

Expect(dnsRecord.Deploy(ctx)).To(Succeed())

deployedDNS := &extensionsv1alpha1.DNSRecord{}
err := c.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, deployedDNS)
Expect(err).NotTo(HaveOccurred())
Expect(deployedDNS).To(DeepEqual(&extensionsv1alpha1.DNSRecord{
TypeMeta: metav1.TypeMeta{
APIVersion: extensionsv1alpha1.SchemeGroupVersion.String(),
Kind: extensionsv1alpha1.DNSRecordResource,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
Annotations: map[string]string{
v1beta1constants.GardenerOperation: v1beta1constants.GardenerOperationReconcile,
v1beta1constants.GardenerTimestamp: now.UTC().Format(time.RFC3339Nano),
"dns.gardener.cloud/ip-stack": "ipv5",
},
ResourceVersion: "1",
},
Spec: dns.Spec,
}))
})

It("should fail if creating the DNSRecord resource failed", func() {
mc := mockclient.NewMockClient(ctrl)
mc.EXPECT().Get(ctx, client.ObjectKeyFromObject(secret), gomock.AssignableToTypeOf(&corev1.Secret{})).
Expand Down
2 changes: 2 additions & 0 deletions pkg/gardenlet/controller/seed/seed/nginxingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/gardener/gardener/pkg/component/extensions/dnsrecord"
seedpkg "github.com/gardener/gardener/pkg/gardenlet/operation/seed"
"github.com/gardener/gardener/pkg/utils"
gardenerutils "github.com/gardener/gardener/pkg/utils/gardener"
kubernetesutils "github.com/gardener/gardener/pkg/utils/kubernetes"
)

Expand All @@ -44,6 +45,7 @@ func (r *Reconciler) newIngressDNSRecord(ctx context.Context, log logr.Logger, s
DNSName: seed.GetIngressFQDN("*"),
RecordType: extensionsv1alpha1helper.GetDNSRecordType(loadBalancerAddress),
ReconcileOnlyOnChangeOrError: true,
IPStack: gardenerutils.GetIPStackForSeed(seed.GetInfo()),
}

if provider := seed.GetInfo().Spec.DNS.Provider; provider != nil {
Expand Down
2 changes: 2 additions & 0 deletions pkg/gardenlet/operation/botanist/dnsrecord.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func (b *Botanist) DefaultExternalDNSRecord() extensionsdnsrecord.Interface {
Namespace: b.Shoot.SeedNamespace,
TTL: b.Config.Controllers.Shoot.DNSEntryTTLSeconds,
AnnotateOperation: controllerutils.HasTask(b.Shoot.GetInfo().Annotations, v1beta1constants.ShootTaskDeployDNSRecordExternal) || b.IsRestorePhase(),
IPStack: gardenerutils.GetIPStackForShoot(b.Shoot.GetInfo()),
}

if b.NeedsExternalDNS() {
Expand Down Expand Up @@ -64,6 +65,7 @@ func (b *Botanist) DefaultInternalDNSRecord() extensionsdnsrecord.Interface {
AnnotateOperation: b.Shoot.GetInfo().DeletionTimestamp != nil ||
controllerutils.HasTask(b.Shoot.GetInfo().Annotations, v1beta1constants.ShootTaskDeployDNSRecordInternal) ||
b.IsRestorePhase(),
IPStack: gardenerutils.GetIPStackForShoot(b.Shoot.GetInfo()),
}

if b.NeedsInternalDNS() {
Expand Down
2 changes: 2 additions & 0 deletions pkg/gardenlet/operation/botanist/dnsrecord_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ var _ = Describe("dnsrecord", func() {
RecordType: extensionsv1alpha1.DNSRecordTypeA,
Values: []string{address},
AnnotateOperation: false,
IPStack: "ipv4",
}))
})

Expand Down Expand Up @@ -312,6 +313,7 @@ var _ = Describe("dnsrecord", func() {
RecordType: extensionsv1alpha1.DNSRecordTypeA,
Values: []string{address},
AnnotateOperation: false,
IPStack: "ipv4",
}))
})

Expand Down
2 changes: 2 additions & 0 deletions pkg/gardenlet/operation/botanist/nginxingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
sharedcomponent "github.com/gardener/gardener/pkg/component/shared"
"github.com/gardener/gardener/pkg/controllerutils"
"github.com/gardener/gardener/pkg/utils"
gardenerutils "github.com/gardener/gardener/pkg/utils/gardener"
)

// DefaultNginxIngress returns a deployer for the nginxingress.
Expand Down Expand Up @@ -103,6 +104,7 @@ func (b *Botanist) DefaultIngressDNSRecord() extensionsdnsrecord.Interface {
Namespace: b.Shoot.SeedNamespace,
TTL: b.Config.Controllers.Shoot.DNSEntryTTLSeconds,
AnnotateOperation: controllerutils.HasTask(b.Shoot.GetInfo().Annotations, v1beta1constants.ShootTaskDeployDNSRecordIngress) || b.IsRestorePhase(),
IPStack: gardenerutils.GetIPStackForShoot(b.Shoot.GetInfo()),
}

// Set component values even if the nginx-ingress addons is not enabled.
Expand Down
2 changes: 2 additions & 0 deletions pkg/gardenlet/operation/botanist/nginxingress_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ var _ = Describe("NginxIngress", func() {
RecordType: extensionsv1alpha1.DNSRecordTypeA,
Values: []string{address},
AnnotateOperation: false,
IPStack: "ipv4",
}))
})

Expand Down Expand Up @@ -279,6 +280,7 @@ var _ = Describe("NginxIngress", func() {
RecordType: extensionsv1alpha1.DNSRecordTypeA,
Values: []string{address},
AnnotateOperation: false,
IPStack: "ipv4",
}))
})

Expand Down
27 changes: 27 additions & 0 deletions pkg/utils/gardener/dns.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ package gardener

import (
"fmt"
"slices"

gardencorev1beta1 "github.com/gardener/gardener/pkg/apis/core/v1beta1"
)

const (
Expand Down Expand Up @@ -48,6 +51,16 @@ const (
// configured internal domain already contains it, it won't be added twice. If it does not contain it, it will be
// appended.
InternalDomainKey = "internal"

// AnnotationKeyIPStack is the annotation key to set the IP stack for a DNSRecord.
// This can be used to create different type of records, e.g. A vs. AAAA records.
AnnotationKeyIPStack = "dns.gardener.cloud/ip-stack"
// AnnotationValueIPStackIPv4 is the annotation value for ipv4-only.
AnnotationValueIPStackIPv4 = "ipv4"
// AnnotationValueIPStackIPv6 is the annotation value for ipv6-only.
AnnotationValueIPStackIPv6 = "ipv6"
// AnnotationValueIPStackIPDualStack is the annotation value for dual-stack, i.e. ipv4 and ipv6.
AnnotationValueIPStackIPDualStack = "dual-stack"
)

// GetDomainInfoFromAnnotations returns the provider, domain, and zones that are specified in the given annotations.
Expand Down Expand Up @@ -103,3 +116,17 @@ func GenerateDNSProviderName(secretName, providerType string) string {
return ""
}
}

func getIPStackForFamilies(ipFamilies []gardencorev1beta1.IPFamily) string {
if gardencorev1beta1.IsIPv4SingleStack(ipFamilies) {
return AnnotationValueIPStackIPv4
}
if gardencorev1beta1.IsIPv6SingleStack(ipFamilies) {
return AnnotationValueIPStackIPv6
}
if len(ipFamilies) == 2 && slices.Contains(ipFamilies, gardencorev1beta1.IPFamilyIPv4) && slices.Contains(ipFamilies, gardencorev1beta1.IPFamilyIPv6) {
return AnnotationValueIPStackIPDualStack
}
// Fall-back to IPv4 per default
return AnnotationValueIPStackIPv4
}
6 changes: 6 additions & 0 deletions pkg/utils/gardener/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,9 @@ func RequiredExtensionsReady(ctx context.Context, gardenClient client.Client, se

return nil
}

// GetIPStackForSeed returns the value for the AnnotationKeyIPStack annotation based on the given seed.
// It falls back to IPv4 if no IP families are available.
func GetIPStackForSeed(seed *gardencorev1beta1.Seed) string {
return getIPStackForFamilies(seed.Spec.Networks.IPFamilies)
}
12 changes: 12 additions & 0 deletions pkg/utils/gardener/seed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,4 +342,16 @@ var _ = Describe("utils", func() {
})
})
})

DescribeTable("#GetIPStackForSeed",
func(seed *gardencorev1beta1.Seed, expectedResult string) {
Expect(GetIPStackForSeed(seed)).To(Equal(expectedResult))
},

Entry("default seed", &gardencorev1beta1.Seed{}, "ipv4"),
Entry("ipv4 seed", &gardencorev1beta1.Seed{Spec: gardencorev1beta1.SeedSpec{Networks: gardencorev1beta1.SeedNetworks{IPFamilies: []gardencorev1beta1.IPFamily{gardencorev1beta1.IPFamilyIPv4}}}}, "ipv4"),
Entry("ipv6 seed", &gardencorev1beta1.Seed{Spec: gardencorev1beta1.SeedSpec{Networks: gardencorev1beta1.SeedNetworks{IPFamilies: []gardencorev1beta1.IPFamily{gardencorev1beta1.IPFamilyIPv6}}}}, "ipv6"),
Entry("dual-stack seed (ipv4 preferred)", &gardencorev1beta1.Seed{Spec: gardencorev1beta1.SeedSpec{Networks: gardencorev1beta1.SeedNetworks{IPFamilies: []gardencorev1beta1.IPFamily{gardencorev1beta1.IPFamilyIPv4, gardencorev1beta1.IPFamilyIPv6}}}}, "dual-stack"),
Entry("dual-stack seed (ipv6 preferred)", &gardencorev1beta1.Seed{Spec: gardencorev1beta1.SeedSpec{Networks: gardencorev1beta1.SeedNetworks{IPFamilies: []gardencorev1beta1.IPFamily{gardencorev1beta1.IPFamilyIPv6, gardencorev1beta1.IPFamilyIPv4}}}}, "dual-stack"),
)
})
10 changes: 10 additions & 0 deletions pkg/utils/gardener/shoot.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,3 +755,13 @@ func DefaultGVKsForEncryption() []schema.GroupVersionKind {
func DefaultResourcesForEncryption() sets.Set[string] {
return sets.New(corev1.Resource("secrets").String())
}

// GetIPStackForShoot returns the value for the AnnotationKeyIPStack annotation based on the given shoot.
// It falls back to IPv4 if no IP families are available, e.g. in a workerless shoot cluster.
func GetIPStackForShoot(shoot *gardencorev1beta1.Shoot) string {
var ipFamilies []gardencorev1beta1.IPFamily
if networking := shoot.Spec.Networking; networking != nil {
ipFamilies = networking.IPFamilies
}
return getIPStackForFamilies(ipFamilies)
}
12 changes: 12 additions & 0 deletions pkg/utils/gardener/shoot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1341,4 +1341,16 @@ var _ = Describe("Shoot", func() {
))
})
})

DescribeTable("#GetIPStackForShoot",
func(shoot *gardencorev1beta1.Shoot, expectedResult string) {
Expect(GetIPStackForShoot(shoot)).To(Equal(expectedResult))
},

Entry("default shoot", &gardencorev1beta1.Shoot{}, "ipv4"),
Entry("ipv4 shoot", &gardencorev1beta1.Shoot{Spec: gardencorev1beta1.ShootSpec{Networking: &gardencorev1beta1.Networking{IPFamilies: []gardencorev1beta1.IPFamily{gardencorev1beta1.IPFamilyIPv4}}}}, "ipv4"),
Entry("ipv6 shoot", &gardencorev1beta1.Shoot{Spec: gardencorev1beta1.ShootSpec{Networking: &gardencorev1beta1.Networking{IPFamilies: []gardencorev1beta1.IPFamily{gardencorev1beta1.IPFamilyIPv6}}}}, "ipv6"),
Entry("dual-stack shoot (ipv4 preferred)", &gardencorev1beta1.Shoot{Spec: gardencorev1beta1.ShootSpec{Networking: &gardencorev1beta1.Networking{IPFamilies: []gardencorev1beta1.IPFamily{gardencorev1beta1.IPFamilyIPv4, gardencorev1beta1.IPFamilyIPv6}}}}, "dual-stack"),
Entry("dual-stack shoot (ipv6 preferred)", &gardencorev1beta1.Shoot{Spec: gardencorev1beta1.ShootSpec{Networking: &gardencorev1beta1.Networking{IPFamilies: []gardencorev1beta1.IPFamily{gardencorev1beta1.IPFamilyIPv6, gardencorev1beta1.IPFamilyIPv4}}}}, "dual-stack"),
)
})

0 comments on commit bdffece

Please sign in to comment.