istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/dns/server/name_table.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package server 16 17 import ( 18 "strings" 19 20 "istio.io/istio/pilot/pkg/model" 21 "istio.io/istio/pilot/pkg/serviceregistry/provider" 22 "istio.io/istio/pkg/config/constants" 23 dnsProto "istio.io/istio/pkg/dns/proto" 24 netutil "istio.io/istio/pkg/util/net" 25 ) 26 27 // Config for building the name table. 28 type Config struct { 29 Node *model.Proxy 30 Push *model.PushContext 31 32 // MulticlusterHeadlessEnabled if true, the DNS name table for a headless service will resolve to 33 // same-network endpoints in any cluster. 34 MulticlusterHeadlessEnabled bool 35 } 36 37 // BuildNameTable produces a table of hostnames and their associated IPs that can then 38 // be used by the agent to resolve DNS. This logic is always active. However, local DNS resolution 39 // will only be effective if DNS capture is enabled in the proxy 40 func BuildNameTable(cfg Config) *dnsProto.NameTable { 41 out := &dnsProto.NameTable{ 42 Table: make(map[string]*dnsProto.NameTable_NameInfo), 43 } 44 for _, svc := range cfg.Node.SidecarScope.Services() { 45 svcAddress := svc.GetAddressForProxy(cfg.Node) 46 var addressList []string 47 hostName := svc.Hostname 48 if svcAddress != constants.UnspecifiedIP { 49 // Filter out things we cannot parse as IP. Generally this means CIDRs, as anything else 50 // should be caught in validation. 51 if !netutil.IsValidIPAddress(svcAddress) { 52 continue 53 } 54 addressList = append(addressList, svcAddress) 55 } else { 56 // The IP will be unspecified here if its headless service or if the auto 57 // IP allocation logic for service entry was unable to allocate an IP. 58 if svc.Resolution == model.Passthrough && len(svc.Ports) > 0 { 59 for _, instance := range cfg.Push.ServiceEndpointsByPort(svc, svc.Ports[0].Port, nil) { 60 // empty addresses are possible here 61 if !netutil.IsValidIPAddress(instance.Address) { 62 continue 63 } 64 // TODO(stevenctl): headless across-networks https://github.com/istio/istio/issues/38327 65 sameNetwork := cfg.Node.InNetwork(instance.Network) 66 sameCluster := cfg.Node.InCluster(instance.Locality.ClusterID) 67 // For all k8s headless services, populate the dns table with the endpoint IPs as k8s does. 68 // And for each individual pod, populate the dns table with the endpoint IP with a manufactured host name. 69 if instance.SubDomain != "" && sameNetwork { 70 // Follow k8s pods dns naming convention of "<hostname>.<subdomain>.<pod namespace>.svc.<cluster domain>" 71 // i.e. "mysql-0.mysql.default.svc.cluster.local". 72 parts := strings.SplitN(hostName.String(), ".", 2) 73 if len(parts) != 2 { 74 continue 75 } 76 address := []string{instance.Address} 77 shortName := instance.HostName + "." + instance.SubDomain 78 host := shortName + "." + parts[1] // Add cluster domain. 79 nameInfo := &dnsProto.NameTable_NameInfo{ 80 Ips: address, 81 Registry: string(svc.Attributes.ServiceRegistry), 82 Namespace: svc.Attributes.Namespace, 83 Shortname: shortName, 84 } 85 86 if _, f := out.Table[host]; !f || sameCluster { 87 // We may have the same pod in two clusters (ie mysql-0 deployed in both places). 88 // We can only return a single IP for these queries. We should prefer the local cluster, 89 // so if the entry already exists only overwrite it if the instance is in our own cluster. 90 out.Table[host] = nameInfo 91 } 92 } 93 skipForMulticluster := !cfg.MulticlusterHeadlessEnabled && !sameCluster 94 if skipForMulticluster || !sameNetwork { 95 // We take only cluster-local endpoints. While this seems contradictory to 96 // our logic other parts of the code, where cross-cluster is the default. 97 // However, this only impacts the DNS response. If we were to send all 98 // endpoints, cross network routing would break, as we do passthrough LB and 99 // don't go through the network gateway. While we could, hypothetically, send 100 // "network-local" endpoints, this would still make enabling DNS give vastly 101 // different load balancing than without, so its probably best to filter. 102 // This ends up matching the behavior of Kubernetes DNS. 103 continue 104 } 105 // TODO: should we skip the node's own IP like we do in listener? 106 addressList = append(addressList, instance.Address) 107 } 108 } 109 if len(addressList) == 0 { 110 // could not reliably determine the addresses of endpoints of headless service 111 // or this is not a k8s service 112 continue 113 } 114 } 115 116 if ni, f := out.Table[hostName.String()]; !f { 117 nameInfo := &dnsProto.NameTable_NameInfo{ 118 Ips: addressList, 119 Registry: string(svc.Attributes.ServiceRegistry), 120 } 121 if svc.Attributes.ServiceRegistry == provider.Kubernetes && 122 !strings.HasSuffix(hostName.String(), "."+constants.DefaultClusterSetLocalDomain) { 123 // The agent will take care of resolving a, a.ns, a.ns.svc, etc. 124 // No need to provide a DNS entry for each variant. 125 // 126 // NOTE: This is not done for Kubernetes Multi-Cluster Services (MCS) hosts, in order 127 // to avoid conflicting with the entries for the regular (cluster.local) service. 128 nameInfo.Namespace = svc.Attributes.Namespace 129 nameInfo.Shortname = svc.Attributes.Name 130 } 131 out.Table[hostName.String()] = nameInfo 132 } else if provider.ID(ni.Registry) != provider.Kubernetes { 133 // 2 possible cases: 134 // 1. If the SE has multiple addresses(vips) specified, merge the ips 135 // 2. If the previous SE is a decorator of the k8s service, give precedence to the k8s service 136 if svc.Attributes.ServiceRegistry == provider.Kubernetes { 137 ni.Ips = addressList 138 ni.Registry = string(provider.Kubernetes) 139 if !strings.HasSuffix(hostName.String(), "."+constants.DefaultClusterSetLocalDomain) { 140 ni.Namespace = svc.Attributes.Namespace 141 ni.Shortname = svc.Attributes.Name 142 } 143 } else { 144 ni.Ips = append(ni.Ips, addressList...) 145 } 146 } 147 } 148 return out 149 }