Create placeholder DNS records of correct type for IPv6 clusters

This commit is contained in:
John Gardiner Myers 2021-10-26 17:26:29 -07:00
parent 3a056c288b
commit d4cf1a80f0
6 changed files with 135 additions and 42 deletions

View File

@ -13,6 +13,7 @@ go_library(
"//pkg/cloudinstances:go_default_library",
"//pkg/dns:go_default_library",
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",

View File

@ -27,6 +27,7 @@ import (
"k8s.io/client-go/tools/pager"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -90,7 +91,7 @@ func hasPlaceHolderIP(host string) (bool, error) {
}
for _, h := range hostAddrs {
if h == "203.0.113.123" {
if h == cloudup.PlaceholderIP || h == cloudup.PlaceholderIPv6 {
return true, nil
}
}

View File

@ -115,6 +115,7 @@ go_test(
embed = [":go_default_library"],
deps = [
"//:go_default_library",
"//dnsprovider/pkg/dnsprovider/rrstype:go_default_library",
"//pkg/apis/kops:go_default_library",
"//pkg/apis/kops/validation:go_default_library",
"//pkg/assets:go_default_library",

View File

@ -40,11 +40,17 @@ const (
// PlaceholderIP is from TEST-NET-3
// https://en.wikipedia.org/wiki/Reserved_IP_addresses
PlaceholderIP = "203.0.113.123"
PlaceholderIPv6 = "fd00:dead:add::"
PlaceholderTTL = 10
// DigitalOcean's DNS servers require a certain minimum TTL (it's 30), keeping 60 here.
PlaceholderTTLDigitialOcean = 60
)
type recordKey struct {
hostname string
rrsType rrstype.RrsType
}
func findZone(cluster *kops.Cluster, cloud fi.Cloud) (dnsprovider.Zone, error) {
dns, err := cloud.DNS()
if err != nil {
@ -137,19 +143,19 @@ func precreateDNS(ctx context.Context, cluster *kops.Cluster, cloud fi.Cloud) er
// This avoids hitting negative TTL on DNS lookups, which tend to be very long
// If we get the names wrong here, it doesn't really matter (extra DNS name, slower boot)
dnsHostnames := buildPrecreateDNSHostnames(cluster)
recordKeys := buildPrecreateDNSHostnames(cluster)
{
var filtered []string
for _, name := range dnsHostnames {
if !kopsdns.IsGossipHostname(name) {
filtered = append(filtered, name)
var filtered []recordKey
for _, recordKey := range recordKeys {
if !kopsdns.IsGossipHostname(recordKey.hostname) {
filtered = append(filtered, recordKey)
}
}
dnsHostnames = filtered
recordKeys = filtered
}
if len(dnsHostnames) == 0 {
if len(recordKeys) == 0 {
klog.V(2).Infof("No DNS records to pre-create")
return nil
}
@ -182,52 +188,56 @@ func precreateDNS(ctx context.Context, cluster *kops.Cluster, cloud fi.Cloud) er
changeset := rrs.StartChangeset()
// TODO: Add ChangeSet.IsEmpty() method
var created []string
var created []recordKey
for _, dnsHostname := range dnsHostnames {
dnsHostname = dns.EnsureDotSuffix(dnsHostname)
foundA := false
for _, recordKey := range recordKeys {
recordKey.hostname = dns.EnsureDotSuffix(recordKey.hostname)
foundAddress := false
{
dnsRecord := recordsMap["A::"+dnsHostname]
dnsRecord := recordsMap[string(recordKey.rrsType)+"::"+recordKey.hostname]
if dnsRecord != nil {
rrdatas := dnsRecord.Rrdatas()
if len(rrdatas) > 0 {
klog.V(4).Infof("Found DNS record %s => %s; won't create", dnsHostname, rrdatas)
klog.V(4).Infof("Found DNS record %s => %s; won't create", recordKey, rrdatas)
} else {
// This is probably an alias target; leave it alone...
klog.V(4).Infof("Found DNS record %s, but no records", dnsHostname)
klog.V(4).Infof("Found DNS record %s, but no records", recordKey)
}
foundA = true
foundAddress = true
}
}
foundTXT := false
{
dnsRecord := recordsMap["TXT::"+dnsHostname]
dnsRecord := recordsMap["TXT::"+recordKey.hostname]
if dnsRecord != nil {
foundTXT = true
}
}
if foundA && foundTXT {
if foundAddress && foundTXT {
continue
}
klog.V(2).Infof("Pre-creating DNS record %s => %s", dnsHostname, PlaceholderIP)
ip := PlaceholderIP
if recordKey.rrsType != rrstype.A {
ip = PlaceholderIPv6
}
klog.V(2).Infof("Pre-creating DNS record %s => %s", recordKey, ip)
if !foundA {
if !foundAddress {
if cloud.ProviderID() == kops.CloudProviderDO {
changeset.Add(rrs.New(dnsHostname, []string{PlaceholderIP}, PlaceholderTTLDigitialOcean, rrstype.A))
changeset.Add(rrs.New(recordKey.hostname, []string{ip}, PlaceholderTTLDigitialOcean, recordKey.rrsType))
} else {
changeset.Add(rrs.New(dnsHostname, []string{PlaceholderIP}, PlaceholderTTL, rrstype.A))
changeset.Add(rrs.New(recordKey.hostname, []string{ip}, PlaceholderTTL, recordKey.rrsType))
}
}
if !foundTXT {
if cluster.Spec.ExternalDNS.Provider == kops.ExternalDNSProviderExternalDNS {
changeset.Add(rrs.New(dnsHostname, []string{fmt.Sprintf("\"heritage=external-dns,external-dns/owner=kops-%s\"", cluster.ObjectMeta.Name)}, PlaceholderTTL, rrstype.TXT))
changeset.Add(rrs.New(recordKey.hostname, []string{fmt.Sprintf("\"heritage=external-dns,external-dns/owner=kops-%s\"", cluster.ObjectMeta.Name)}, PlaceholderTTL, rrstype.TXT))
}
}
created = append(created, dnsHostname)
created = append(created, recordKey)
}
if len(created) != 0 {
@ -244,24 +254,43 @@ func precreateDNS(ctx context.Context, cluster *kops.Cluster, cloud fi.Cloud) er
}
// buildPrecreateDNSHostnames returns the hostnames we should precreate
func buildPrecreateDNSHostnames(cluster *kops.Cluster) []string {
dnsHostnames := []string{}
func buildPrecreateDNSHostnames(cluster *kops.Cluster) []recordKey {
var recordKeys []recordKey
internalType := rrstype.A
if cluster.Spec.IsIPv6Only() {
internalType = rrstype.AAAA
}
hasAPILoadbalancer := cluster.Spec.API != nil && cluster.Spec.API.LoadBalancer != nil
useLBForInternalAPI := hasAPILoadbalancer && cluster.Spec.API.LoadBalancer.UseForInternalApi
if cluster.Spec.MasterPublicName != "" && !hasAPILoadbalancer {
dnsHostnames = append(dnsHostnames, cluster.Spec.MasterPublicName)
recordKeys = append(recordKeys, recordKey{
hostname: cluster.Spec.MasterPublicName,
rrsType: rrstype.A,
})
if internalType != rrstype.A {
recordKeys = append(recordKeys, recordKey{
hostname: cluster.Spec.MasterPublicName,
rrsType: internalType,
})
}
}
if cluster.Spec.MasterInternalName != "" && !useLBForInternalAPI {
dnsHostnames = append(dnsHostnames, cluster.Spec.MasterInternalName)
recordKeys = append(recordKeys, recordKey{
hostname: cluster.Spec.MasterInternalName,
rrsType: internalType,
})
}
if apimodel.UseKopsControllerForNodeBootstrap(cluster) {
name := "kops-controller.internal." + cluster.ObjectMeta.Name
dnsHostnames = append(dnsHostnames, name)
recordKeys = append(recordKeys, recordKey{
hostname: name,
rrsType: internalType,
})
}
return dnsHostnames
return recordKeys
}

View File

@ -21,6 +21,7 @@ import (
"sort"
"testing"
"k8s.io/kops/dnsprovider/pkg/dnsprovider/rrstype"
"k8s.io/kops/pkg/apis/kops"
)
@ -28,13 +29,25 @@ func TestPrecreateDNSNames(t *testing.T) {
grid := []struct {
cluster *kops.Cluster
expected []string
expected []recordKey
}{
{
cluster: &kops.Cluster{},
expected: []string{
"api.cluster1.example.com",
"api.internal.cluster1.example.com",
expected: []recordKey{
{"api.cluster1.example.com", rrstype.A},
{"api.internal.cluster1.example.com", rrstype.A},
},
},
{
cluster: &kops.Cluster{
Spec: kops.ClusterSpec{
NonMasqueradeCIDR: "::/0",
},
},
expected: []recordKey{
{"api.cluster1.example.com", rrstype.A},
{"api.cluster1.example.com", rrstype.AAAA},
{"api.internal.cluster1.example.com", rrstype.AAAA},
},
},
{
@ -45,8 +58,21 @@ func TestPrecreateDNSNames(t *testing.T) {
},
},
},
expected: []string{
"api.internal.cluster1.example.com",
expected: []recordKey{
{"api.internal.cluster1.example.com", rrstype.A},
},
},
{
cluster: &kops.Cluster{
Spec: kops.ClusterSpec{
API: &kops.AccessSpec{
LoadBalancer: &kops.LoadBalancerAccessSpec{},
},
NonMasqueradeCIDR: "::/0",
},
},
expected: []recordKey{
{"api.internal.cluster1.example.com", rrstype.AAAA},
},
},
{
@ -59,7 +85,35 @@ func TestPrecreateDNSNames(t *testing.T) {
},
},
},
expected: []string{},
expected: nil,
},
{
cluster: &kops.Cluster{
Spec: kops.ClusterSpec{
CloudProvider: "aws",
KubernetesVersion: "1.22.0",
},
},
expected: []recordKey{
{"api.cluster1.example.com", rrstype.A},
{"api.internal.cluster1.example.com", rrstype.A},
{"kops-controller.internal.cluster1.example.com", rrstype.A},
},
},
{
cluster: &kops.Cluster{
Spec: kops.ClusterSpec{
CloudProvider: "aws",
KubernetesVersion: "1.22.0",
NonMasqueradeCIDR: "::/0",
},
},
expected: []recordKey{
{"api.cluster1.example.com", rrstype.A},
{"api.cluster1.example.com", rrstype.AAAA},
{"api.internal.cluster1.example.com", rrstype.AAAA},
{"kops-controller.internal.cluster1.example.com", rrstype.AAAA},
},
},
}
@ -91,8 +145,15 @@ func TestPrecreateDNSNames(t *testing.T) {
actual := buildPrecreateDNSHostnames(cluster)
expected := g.expected
sort.Strings(actual)
sort.Strings(expected)
sort.Slice(actual, func(i, j int) bool {
if actual[i].hostname < actual[j].hostname {
return true
}
if actual[i].hostname == actual[j].hostname && actual[i].rrsType < actual[j].rrsType {
return true
}
return false
})
if !reflect.DeepEqual(expected, actual) {
t.Errorf("unexpected records. expected=%v actual=%v", expected, actual)

View File

@ -167,7 +167,7 @@ func (b *KopsBootstrapClient) QueryBootstrap(ctx context.Context, req *nodeup.Bo
return nil, fi.NewTryAgainLaterError(fmt.Sprintf("kops-controller DNS not setup yet (not found: %v)", dnsErr))
}
return nil, err
} else if len(ips) == 1 && ips[0].String() == cloudup.PlaceholderIP {
} else if len(ips) == 1 && (ips[0].String() == cloudup.PlaceholderIP || ips[0].String() == cloudup.PlaceholderIPv6) {
return nil, fi.NewTryAgainLaterError(fmt.Sprintf("kops-controller DNS not setup yet (placeholder IP found: %v)", ips))
}