2016-06-06 12:49:53 -07:00
// Package kubernetes provides the kubernetes backend.
package kubernetes
import (
2019-03-26 14:37:30 +00:00
"context"
2016-07-07 01:40:58 -07:00
"errors"
2016-10-30 15:54:16 +00:00
"fmt"
2017-02-01 12:56:10 -05:00
"net"
2024-08-26 22:45:39 +02:00
"runtime"
2016-09-23 09:48:11 -03:00
"strings"
2021-03-26 08:54:39 -04:00
"time"
2016-06-06 12:49:53 -07:00
2024-08-26 22:45:39 +02:00
"github.com/coredns/coredns/coremain"
2017-09-14 09:36:06 +01:00
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/etcd/msg"
2018-10-09 21:56:09 +01:00
"github.com/coredns/coredns/plugin/kubernetes/object"
2017-09-14 09:36:06 +01:00
"github.com/coredns/coredns/plugin/pkg/dnsutil"
2018-01-07 16:32:59 +00:00
"github.com/coredns/coredns/plugin/pkg/fall"
2017-02-21 22:51:47 -08:00
"github.com/coredns/coredns/request"
2016-06-06 12:49:53 -07:00
2016-07-07 01:40:58 -07:00
"github.com/miekg/dns"
2018-01-03 19:11:28 +08:00
api "k8s.io/api/core/v1"
2017-09-29 15:58:50 -04:00
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2025-05-19 07:58:16 +02:00
mcsClientset "sigs.k8s.io/mcs-api/pkg/client/clientset/versioned/typed/apis/v1alpha1"
2016-08-05 18:19:51 -07:00
)
2017-09-14 09:36:06 +01:00
// Kubernetes implements a plugin that connects to a Kubernetes cluster.
2016-06-06 12:49:53 -07:00
type Kubernetes struct {
2017-11-08 08:07:10 -05:00
Next plugin . Handler
Zones [ ] string
2022-02-22 09:38:57 -05:00
Upstream Upstreamer
2017-11-08 08:07:10 -05:00
APIServerList [ ] string
APICertAuth string
APIClientCert string
APIClientKey string
2018-09-28 12:18:55 -07:00
ClientConfig clientcmd . ClientConfig
2017-11-08 08:07:10 -05:00
APIConn dnsController
2018-12-08 23:40:07 +00:00
Namespaces map [ string ] struct { }
2017-11-08 08:07:10 -05:00
podMode string
endpointNameMode bool
2018-01-07 14:51:32 -05:00
Fall fall . F
2017-11-08 08:07:10 -05:00
ttl uint32
2018-02-12 14:27:16 -05:00
opts dnsControlOpts
2019-09-05 09:07:55 -04:00
primaryZoneIndex int
localIPs [ ] net . IP
2025-06-12 02:22:07 +08:00
autoPathSearch [ ] string // Local search path from /etc/resolv.conf. Needed for autopath.
startupTimeout time . Duration // startupTimeout set timeout of startup
2017-06-28 18:44:30 -04:00
}
2022-02-22 09:38:57 -05:00
// Upstreamer is used to resolve CNAME or other external targets
type Upstreamer interface {
Lookup ( ctx context . Context , state request . Request , name string , typ uint16 ) ( * dns . Msg , error )
}
2018-01-29 13:16:13 -05:00
// New returns a initialized Kubernetes. It default interfaceAddrFunc to return 127.0.0.1. All other
2017-08-18 14:45:20 +01:00
// values default to their zero value, primaryZoneIndex will thus point to the first zone.
func New ( zones [ ] string ) * Kubernetes {
k := new ( Kubernetes )
k . Zones = zones
2018-12-08 23:40:07 +00:00
k . Namespaces = make ( map [ string ] struct { } )
2017-08-22 22:11:48 +01:00
k . podMode = podModeDisabled
2017-08-27 01:32:46 +01:00
k . ttl = defaultTTL
2017-08-18 14:45:20 +01:00
return k
}
2017-01-11 16:23:10 -05:00
const (
2017-08-22 22:11:48 +01:00
// podModeDisabled is the default value where pod requests are ignored
podModeDisabled = "disabled"
// podModeVerified is where Pod requests are answered only if they exist
podModeVerified = "verified"
2019-08-15 21:15:23 +08:00
// podModeInsecure is where pod requests are answered without verifying they exist
2017-08-22 22:11:48 +01:00
podModeInsecure = "insecure"
2017-01-29 12:06:26 -08:00
// DNSSchemaVersion is the schema version: https://github.com/kubernetes/dns/blob/master/docs/specification.md
2019-12-19 15:34:03 +01:00
DNSSchemaVersion = "1.1.0"
2017-12-07 17:04:02 -06:00
// Svc is the DNS schema for kubernetes services
Svc = "svc"
// Pod is the DNS schema for kubernetes pods
Pod = "pod"
// defaultTTL to apply to all answers.
defaultTTL = 5
2017-01-11 16:23:10 -05:00
)
2017-08-03 09:03:53 -07:00
var (
2017-08-06 05:54:24 -07:00
errNoItems = errors . New ( "no items found" )
errNsNotExposed = errors . New ( "namespace is not exposed" )
errInvalidRequest = errors . New ( "invalid query name" )
2017-08-03 09:03:53 -07:00
)
2016-11-10 16:24:06 -05:00
2016-10-30 15:54:16 +00:00
// Services implements the ServiceBackend interface.
2019-03-26 14:37:30 +00:00
func ( k * Kubernetes ) Services ( ctx context . Context , state request . Request , exact bool , opt plugin . Options ) ( svcs [ ] msg . Service , err error ) {
2017-08-10 01:08:58 -07:00
// We're looking again at types, which we've already done in ServeDNS, but there are some types k8s just can't answer.
switch state . QType ( ) {
case dns . TypeTXT :
2017-08-10 22:14:19 +01:00
// 1 label + zone, label must be "dns-version".
2017-08-18 14:45:20 +01:00
t , _ := dnsutil . TrimZone ( state . Name ( ) , state . Zone )
2022-12-13 20:36:46 +00:00
// Hard code the only valid TXT - "dns-version.<zone>"
2017-08-10 01:08:58 -07:00
segs := dns . SplitDomainName ( t )
2022-12-13 20:36:46 +00:00
if len ( segs ) == 1 && segs [ 0 ] == "dns-version" {
svc := msg . Service { Text : DNSSchemaVersion , TTL : 28800 , Key : msg . Path ( state . QName ( ) , coredns ) }
return [ ] msg . Service { svc } , nil
2017-08-10 01:08:58 -07:00
}
2022-12-13 20:36:46 +00:00
// Check if we have an existing record for this query of another type
services , _ := k . Records ( ctx , state , false )
if len ( services ) > 0 {
// If so we return an empty NOERROR
2017-09-12 10:52:43 +01:00
return nil , nil
2017-08-10 01:08:58 -07:00
}
2022-12-13 20:36:46 +00:00
// Return NXDOMAIN for no match
return nil , errNoItems
2017-08-18 14:45:20 +01:00
2017-08-10 22:14:19 +01:00
case dns . TypeNS :
2018-06-12 03:23:25 +01:00
// We can only get here if the qname equals the zone, see ServeDNS in handler.go.
2022-08-30 20:59:27 +02:00
nss := k . nsAddrs ( false , false , state . Zone )
2019-08-23 12:54:06 -04:00
var svcs [ ] msg . Service
for _ , ns := range nss {
if ns . Header ( ) . Rrtype == dns . TypeA {
svcs = append ( svcs , msg . Service { Host : ns . ( * dns . A ) . A . String ( ) , Key : msg . Path ( ns . Header ( ) . Name , coredns ) , TTL : k . ttl } )
continue
}
if ns . Header ( ) . Rrtype == dns . TypeAAAA {
svcs = append ( svcs , msg . Service { Host : ns . ( * dns . AAAA ) . AAAA . String ( ) , Key : msg . Path ( ns . Header ( ) . Name , coredns ) , TTL : k . ttl } )
}
}
return svcs , nil
2017-08-10 01:08:58 -07:00
}
2019-05-01 07:42:38 -07:00
if isDefaultNS ( state . Name ( ) , state . Zone ) {
2022-08-30 20:59:27 +02:00
nss := k . nsAddrs ( false , false , state . Zone )
2019-08-23 12:54:06 -04:00
var svcs [ ] msg . Service
for _ , ns := range nss {
if ns . Header ( ) . Rrtype == dns . TypeA && state . QType ( ) == dns . TypeA {
svcs = append ( svcs , msg . Service { Host : ns . ( * dns . A ) . A . String ( ) , Key : msg . Path ( state . QName ( ) , coredns ) , TTL : k . ttl } )
continue
}
if ns . Header ( ) . Rrtype == dns . TypeAAAA && state . QType ( ) == dns . TypeAAAA {
svcs = append ( svcs , msg . Service { Host : ns . ( * dns . AAAA ) . AAAA . String ( ) , Key : msg . Path ( state . QName ( ) , coredns ) , TTL : k . ttl } )
}
2019-05-01 07:42:38 -07:00
}
2019-08-23 12:54:06 -04:00
return svcs , nil
2017-01-05 10:09:59 -05:00
}
2017-08-10 01:08:58 -07:00
2019-03-26 14:37:30 +00:00
s , e := k . Records ( ctx , state , false )
2017-08-18 14:45:20 +01:00
// SRV for external services is not yet implemented, so remove those records.
if state . QType ( ) != dns . TypeSRV {
2017-09-12 10:52:43 +01:00
return s , e
2017-08-18 14:45:20 +01:00
}
internal := [ ] msg . Service { }
for _ , svc := range s {
if t , _ := svc . HostType ( ) ; t != dns . TypeCNAME {
internal = append ( internal , svc )
2017-05-30 08:20:39 -04:00
}
2017-01-15 14:37:18 +00:00
}
2017-08-18 14:45:20 +01:00
2017-09-12 10:52:43 +01:00
return internal , e
2017-01-15 14:37:18 +00:00
}
2017-09-14 09:36:06 +01:00
// primaryZone will return the first non-reverse zone being handled by this plugin
2017-09-12 10:52:43 +01:00
func ( k * Kubernetes ) primaryZone ( ) string { return k . Zones [ k . primaryZoneIndex ] }
2016-11-14 19:31:08 +00:00
2016-10-30 15:54:16 +00:00
// Lookup implements the ServiceBackend interface.
2019-03-26 14:37:30 +00:00
func ( k * Kubernetes ) Lookup ( ctx context . Context , state request . Request , name string , typ uint16 ) ( * dns . Msg , error ) {
return k . Upstream . Lookup ( ctx , state , name , typ )
2016-10-30 15:54:16 +00:00
}
// IsNameError implements the ServiceBackend interface.
func ( k * Kubernetes ) IsNameError ( err error ) bool {
2017-08-10 22:14:19 +01:00
return err == errNoItems || err == errNsNotExposed || err == errInvalidRequest
2016-10-30 15:54:16 +00:00
}
2016-11-05 07:57:08 -04:00
func ( k * Kubernetes ) getClientConfig ( ) ( * rest . Config , error ) {
2018-09-28 12:18:55 -07:00
if k . ClientConfig != nil {
return k . ClientConfig . ClientConfig ( )
}
2017-06-23 18:02:45 -04:00
loadingRules := & clientcmd . ClientConfigLoadingRules { }
2016-08-05 18:19:51 -07:00
overrides := & clientcmd . ConfigOverrides { }
2016-09-23 18:07:06 -04:00
clusterinfo := clientcmdapi . Cluster { }
authinfo := clientcmdapi . AuthInfo { }
2017-08-10 17:14:56 -07:00
2017-09-29 15:58:50 -04:00
// Connect to API from in cluster
2017-08-23 16:30:19 +01:00
if len ( k . APIServerList ) == 0 {
cc , err := rest . InClusterConfig ( )
if err != nil {
return nil , err
}
2017-09-29 15:58:50 -04:00
cc . ContentType = "application/vnd.kubernetes.protobuf"
2024-08-26 22:45:39 +02:00
cc . UserAgent = fmt . Sprintf ( "%s/%s git_commit:%s (%s/%s/%s)" , coremain . CoreName , coremain . CoreVersion , coremain . GitCommit , runtime . GOOS , runtime . GOARCH , runtime . Version ( ) )
2017-08-23 16:30:19 +01:00
return cc , err
}
2017-09-29 15:58:50 -04:00
// Connect to API from out of cluster
2019-08-21 16:08:55 -04:00
// Only the first one is used. We will deprecate multiple endpoints later.
2019-01-13 10:09:51 -08:00
clusterinfo . Server = k . APIServerList [ 0 ]
2017-08-23 16:30:19 +01:00
2016-09-23 18:07:06 -04:00
if len ( k . APICertAuth ) > 0 {
clusterinfo . CertificateAuthority = k . APICertAuth
2016-08-05 18:19:51 -07:00
}
2016-09-23 18:07:06 -04:00
if len ( k . APIClientCert ) > 0 {
authinfo . ClientCertificate = k . APIClientCert
}
if len ( k . APIClientKey ) > 0 {
authinfo . ClientKey = k . APIClientKey
}
2017-08-18 14:45:20 +01:00
2016-09-23 18:07:06 -04:00
overrides . ClusterInfo = clusterinfo
overrides . AuthInfo = authinfo
2016-08-05 18:19:51 -07:00
clientConfig := clientcmd . NewNonInteractiveDeferredLoadingClientConfig ( loadingRules , overrides )
2017-08-18 14:45:20 +01:00
2017-09-29 15:58:50 -04:00
cc , err := clientConfig . ClientConfig ( )
2017-11-13 11:01:57 -05:00
if err != nil {
return nil , err
}
2017-09-29 15:58:50 -04:00
cc . ContentType = "application/vnd.kubernetes.protobuf"
2024-08-26 22:45:39 +02:00
cc . UserAgent = fmt . Sprintf ( "%s/%s git_commit:%s (%s/%s/%s)" , coremain . CoreName , coremain . CoreVersion , coremain . GitCommit , runtime . GOOS , runtime . GOARCH , runtime . Version ( ) )
2017-09-29 15:58:50 -04:00
return cc , err
2016-09-23 18:07:06 -04:00
}
2018-02-12 14:27:16 -05:00
// InitKubeCache initializes a new Kubernetes cache.
2021-03-26 08:54:39 -04:00
func ( k * Kubernetes ) InitKubeCache ( ctx context . Context ) ( onStart func ( ) error , onShut func ( ) error , err error ) {
2016-09-23 18:07:06 -04:00
config , err := k . getClientConfig ( )
2016-08-05 18:19:51 -07:00
if err != nil {
2021-03-26 08:54:39 -04:00
return nil , nil , err
2016-08-05 18:19:51 -07:00
}
2016-11-05 07:57:08 -04:00
kubeClient , err := kubernetes . NewForConfig ( config )
2016-08-05 18:19:51 -07:00
if err != nil {
2021-03-26 08:54:39 -04:00
return nil , nil , fmt . Errorf ( "failed to create kubernetes notification controller: %q" , err )
2016-08-05 18:19:51 -07:00
}
2016-10-30 15:54:16 +00:00
2025-05-19 07:58:16 +02:00
var mcsClient mcsClientset . MulticlusterV1alpha1Interface
if len ( k . opts . multiclusterZones ) > 0 {
mcsClient , err = mcsClientset . NewForConfig ( config )
if err != nil {
return nil , nil , fmt . Errorf ( "failed to create kubernetes multicluster notification controller: %q" , err )
}
}
2018-02-12 14:27:16 -05:00
if k . opts . labelSelector != nil {
2016-08-19 17:14:17 -07:00
var selector labels . Selector
2018-02-12 14:27:16 -05:00
selector , err = meta . LabelSelectorAsSelector ( k . opts . labelSelector )
2016-08-19 17:14:17 -07:00
if err != nil {
2021-03-26 08:54:39 -04:00
return nil , nil , fmt . Errorf ( "unable to create Selector for LabelSelector '%s': %q" , k . opts . labelSelector , err )
2016-08-19 17:14:17 -07:00
}
2018-02-12 14:27:16 -05:00
k . opts . selector = selector
2016-10-30 15:54:16 +00:00
}
2019-03-22 08:32:40 -06:00
if k . opts . namespaceLabelSelector != nil {
var selector labels . Selector
selector , err = meta . LabelSelectorAsSelector ( k . opts . namespaceLabelSelector )
if err != nil {
2021-03-26 08:54:39 -04:00
return nil , nil , fmt . Errorf ( "unable to create Selector for LabelSelector '%s': %q" , k . opts . namespaceLabelSelector , err )
2019-03-22 08:32:40 -06:00
}
k . opts . namespaceSelector = selector
}
2018-02-12 14:27:16 -05:00
k . opts . initPodCache = k . podMode == podModeVerified
2016-10-30 15:54:16 +00:00
2018-06-27 07:45:32 -07:00
k . opts . zones = k . Zones
k . opts . endpointNameMode = k . endpointNameMode
2021-03-26 08:54:39 -04:00
2025-05-19 07:58:16 +02:00
k . APIConn = newdnsController ( ctx , kubeClient , mcsClient , k . opts )
2021-03-26 08:54:39 -04:00
onStart = func ( ) error {
go func ( ) {
k . APIConn . Run ( )
} ( )
2025-06-12 02:22:07 +08:00
timeoutTicker := time . NewTicker ( k . startupTimeout )
2022-03-04 02:36:02 -05:00
defer timeoutTicker . Stop ( )
logDelay := 500 * time . Millisecond
logTicker := time . NewTicker ( logDelay )
defer logTicker . Stop ( )
checkSyncTicker := time . NewTicker ( 100 * time . Millisecond )
defer checkSyncTicker . Stop ( )
2021-03-26 08:54:39 -04:00
for {
select {
2022-03-04 02:36:02 -05:00
case <- checkSyncTicker . C :
2021-03-26 08:54:39 -04:00
if k . APIConn . HasSynced ( ) {
return nil
}
2022-03-04 02:36:02 -05:00
case <- logTicker . C :
2022-02-22 09:21:45 -05:00
log . Info ( "waiting for Kubernetes API before starting server" )
2022-03-04 02:36:02 -05:00
case <- timeoutTicker . C :
2021-03-26 08:54:39 -04:00
log . Warning ( "starting server with unsynced Kubernetes API" )
return nil
}
2021-02-25 18:14:57 +01:00
}
2020-10-30 08:14:30 -04:00
}
2021-03-26 08:54:39 -04:00
onShut = func ( ) error {
return k . APIConn . Stop ( )
}
2016-08-22 23:15:21 -07:00
2021-03-26 08:54:39 -04:00
return onStart , onShut , err
}
2017-08-19 14:03:03 +01:00
// Records looks up services in kubernetes.
2019-03-26 14:37:30 +00:00
func ( k * Kubernetes ) Records ( ctx context . Context , state request . Request , exact bool ) ( [ ] msg . Service , error ) {
2025-05-19 07:58:16 +02:00
multicluster := k . isMultiClusterZone ( state . Zone )
r , e := parseRequest ( state . Name ( ) , state . Zone , multicluster )
2017-08-18 14:45:20 +01:00
if e != nil {
return nil , e
}
2018-04-18 12:12:28 -04:00
if r . podOrSvc == "" {
return nil , nil
}
2017-08-18 14:45:20 +01:00
2018-02-28 08:43:19 -08:00
if dnsutil . IsReverse ( state . Name ( ) ) > 0 {
2018-02-28 10:53:12 -05:00
return nil , errNoItems
}
2022-02-09 09:25:10 -05:00
if ! k . namespaceExposed ( r . namespace ) {
2016-11-11 16:56:15 +00:00
return nil , errNsNotExposed
2016-07-07 01:40:58 -07:00
}
2017-08-18 14:45:20 +01:00
2017-08-23 07:19:41 +01:00
if r . podOrSvc == Pod {
pods , err := k . findPods ( r , state . Zone )
return pods , err
2016-07-07 01:40:58 -07:00
}
2016-06-06 12:49:53 -07:00
2025-05-19 07:58:16 +02:00
var services [ ] msg . Service
var err error
if ! multicluster {
services , err = k . findServices ( r , state . Zone )
} else {
services , err = k . findMultiClusterServices ( r , state . Zone )
}
2017-08-23 07:19:41 +01:00
return services , err
2016-07-07 01:40:58 -07:00
}
2016-06-06 12:49:53 -07:00
2018-10-09 21:56:09 +01:00
func endpointHostname ( addr object . EndpointAddress , endpointNameMode bool ) string {
2017-01-05 10:09:59 -05:00
if addr . Hostname != "" {
2018-09-22 15:12:02 +01:00
return addr . Hostname
2017-01-05 10:09:59 -05:00
}
2018-10-09 21:56:09 +01:00
if endpointNameMode && addr . TargetRefName != "" {
return addr . TargetRefName
2017-11-08 08:07:10 -05:00
}
2017-01-05 10:09:59 -05:00
if strings . Contains ( addr . IP , "." ) {
2025-04-04 20:27:39 +02:00
return strings . ReplaceAll ( addr . IP , "." , "-" )
2017-01-05 10:09:59 -05:00
}
if strings . Contains ( addr . IP , ":" ) {
2025-08-04 19:53:40 -04:00
ipv6Hostname := strings . ReplaceAll ( addr . IP , ":" , "-" )
if strings . HasSuffix ( ipv6Hostname , "-" ) {
return ipv6Hostname + "0"
}
return ipv6Hostname
2017-01-05 10:09:59 -05:00
}
return ""
}
2017-08-23 07:19:41 +01:00
func ( k * Kubernetes ) findPods ( r recordRequest , zone string ) ( pods [ ] msg . Service , err error ) {
2017-08-22 22:11:48 +01:00
if k . podMode == podModeDisabled {
2017-11-08 13:58:48 -05:00
return nil , errNoItems
2017-01-11 16:23:10 -05:00
}
2017-08-23 07:19:41 +01:00
namespace := r . namespace
2022-02-09 09:25:10 -05:00
if ! k . namespaceExposed ( namespace ) {
2019-03-22 08:32:40 -06:00
return nil , errNoItems
}
2017-08-23 07:19:41 +01:00
podname := r . service
2017-11-13 21:51:51 +00:00
2018-08-27 14:41:04 -04:00
// handle empty pod name
if podname == "" {
2022-02-09 09:25:10 -05:00
if k . namespaceExposed ( namespace ) {
2018-08-27 14:41:04 -04:00
// NODATA
return nil , nil
}
// NXDOMAIN
return nil , errNoItems
}
2019-03-22 08:32:40 -06:00
zonePath := msg . Path ( zone , coredns )
2025-06-02 02:30:41 +03:00
var ip string
2017-01-11 16:23:10 -05:00
if strings . Count ( podname , "-" ) == 3 && ! strings . Contains ( podname , "--" ) {
2021-12-01 22:26:18 +08:00
ip = strings . ReplaceAll ( podname , "-" , "." )
2017-01-11 16:23:10 -05:00
} else {
2021-12-01 22:26:18 +08:00
ip = strings . ReplaceAll ( podname , "-" , ":" )
2017-01-11 16:23:10 -05:00
}
2017-08-22 22:11:48 +01:00
if k . podMode == podModeInsecure {
2022-02-09 09:25:10 -05:00
if ! k . namespaceExposed ( namespace ) { // namespace does not exist
2018-01-05 17:48:08 +00:00
return nil , errNoItems
}
2018-01-06 15:56:54 +00:00
// If ip does not parse as an IP address, we return an error, otherwise we assume a CNAME and will try to resolve it in backend_lookup.go
if net . ParseIP ( ip ) == nil {
return nil , errNoItems
}
2018-01-05 17:48:08 +00:00
return [ ] msg . Service { { Key : strings . Join ( [ ] string { zonePath , Pod , namespace , podname } , "/" ) , Host : ip , TTL : k . ttl } } , err
}
2018-08-27 14:41:04 -04:00
// PodModeVerified
2018-01-05 17:48:08 +00:00
err = errNoItems
2017-01-11 16:23:10 -05:00
2017-09-29 15:58:50 -04:00
for _ , p := range k . APIConn . PodIndex ( ip ) {
2017-01-20 02:22:11 -05:00
// check for matching ip and namespace
2018-10-09 21:56:09 +01:00
if ip == p . PodIP && match ( namespace , p . Namespace ) {
2017-11-15 14:06:37 +00:00
s := msg . Service { Key : strings . Join ( [ ] string { zonePath , Pod , namespace , podname } , "/" ) , Host : ip , TTL : k . ttl }
2017-01-20 02:22:11 -05:00
pods = append ( pods , s )
2017-01-11 16:23:10 -05:00
2017-08-23 07:19:41 +01:00
err = nil
}
2017-01-15 03:12:28 -05:00
}
2017-08-23 07:19:41 +01:00
return pods , err
2017-01-15 03:12:28 -05:00
}
2017-08-23 07:19:41 +01:00
// findServices returns the services matching r from the cache.
func ( k * Kubernetes ) findServices ( r recordRequest , zone string ) ( services [ ] msg . Service , err error ) {
2022-02-09 09:25:10 -05:00
if ! k . namespaceExposed ( r . namespace ) {
2019-03-22 08:32:40 -06:00
return nil , errNoItems
}
// handle empty service name
if r . service == "" {
2022-02-09 09:25:10 -05:00
if k . namespaceExposed ( r . namespace ) {
2019-03-22 08:32:40 -06:00
// NODATA
return nil , nil
}
// NXDOMAIN
return nil , errNoItems
}
2017-11-13 21:51:51 +00:00
err = errNoItems
2017-10-24 12:44:34 +01:00
2017-10-17 21:30:54 -04:00
var (
2018-10-09 21:56:09 +01:00
endpointsListFunc func ( ) [ ] * object . Endpoints
endpointsList [ ] * object . Endpoints
serviceList [ ] * object . Service
2017-10-17 21:30:54 -04:00
)
2017-10-24 12:44:34 +01:00
2022-02-09 09:25:10 -05:00
idx := object . ServiceKey ( r . service , r . namespace )
serviceList = k . APIConn . SvcIndex ( idx )
endpointsListFunc = func ( ) [ ] * object . Endpoints { return k . APIConn . EpIndex ( idx ) }
2019-03-22 08:32:40 -06:00
zonePath := msg . Path ( zone , coredns )
2017-10-17 21:30:54 -04:00
for _ , svc := range serviceList {
2025-04-04 20:27:39 +02:00
if ! match ( r . namespace , svc . Namespace ) || ! match ( r . service , svc . Name ) {
2017-01-05 10:09:59 -05:00
continue
}
2017-08-18 14:45:20 +01:00
2019-05-07 20:31:50 -04:00
// If "ignore empty_service" option is set and no endpoints exist, return NXDOMAIN unless
// it's a headless or externalName service (covered below).
2020-12-21 05:30:24 -05:00
if k . opts . ignoreEmptyService && svc . Type != api . ServiceTypeExternalName && ! svc . Headless ( ) { // serve NXDOMAIN if no endpoint is able to answer
2018-05-23 14:57:59 +02:00
podsCount := 0
for _ , ep := range endpointsListFunc ( ) {
for _ , eps := range ep . Subsets {
2021-12-01 22:26:18 +08:00
podsCount += len ( eps . Addresses )
2018-05-23 14:57:59 +02:00
}
}
if podsCount == 0 {
continue
}
}
2020-12-21 05:30:24 -05:00
// External service
if svc . Type == api . ServiceTypeExternalName {
2023-06-19 05:42:17 -07:00
// External services do not have endpoints, nor can we accept port/protocol pseudo subdomains in an SRV query, so skip this service if endpoint, port, or protocol is non-empty in the request
if r . endpoint != "" || r . port != "" || r . protocol != "" {
2022-11-04 09:54:57 -04:00
continue
}
2020-12-21 05:30:24 -05:00
s := msg . Service { Key : strings . Join ( [ ] string { zonePath , Svc , svc . Namespace , svc . Name } , "/" ) , Host : svc . ExternalName , TTL : k . ttl }
if t , _ := s . HostType ( ) ; t == dns . TypeCNAME {
s . Key = strings . Join ( [ ] string { zonePath , Svc , svc . Namespace , svc . Name } , "/" )
services = append ( services , s )
err = nil
}
continue
}
2017-06-14 10:29:41 -04:00
// Endpoint query or headless service
2020-12-21 05:30:24 -05:00
if svc . Headless ( ) || r . endpoint != "" {
2017-10-24 12:44:34 +01:00
if endpointsList == nil {
endpointsList = endpointsListFunc ( )
}
2020-10-30 08:14:30 -04:00
2017-10-17 21:30:54 -04:00
for _ , ep := range endpointsList {
2020-10-30 08:14:30 -04:00
if object . EndpointsKey ( svc . Name , svc . Namespace ) != ep . Index {
2017-01-05 10:09:59 -05:00
continue
}
2017-08-22 20:44:42 +01:00
2017-06-14 10:29:41 -04:00
for _ , eps := range ep . Subsets {
for _ , addr := range eps . Addresses {
2017-08-22 20:44:42 +01:00
// See comments in parse.go parseRequest about the endpoint handling.
if r . endpoint != "" {
2017-11-08 08:07:10 -05:00
if ! match ( r . endpoint , endpointHostname ( addr , k . endpointNameMode ) ) {
2017-06-14 10:29:41 -04:00
continue
}
2017-08-22 20:44:42 +01:00
}
for _ , p := range eps . Ports {
2022-02-09 09:25:10 -05:00
if ! ( matchPortAndProtocol ( r . port , p . Name , r . protocol , p . Protocol ) ) {
2017-06-14 10:29:41 -04:00
continue
}
2017-08-27 01:32:46 +01:00
s := msg . Service { Host : addr . IP , Port : int ( p . Port ) , TTL : k . ttl }
2017-11-08 08:07:10 -05:00
s . Key = strings . Join ( [ ] string { zonePath , Svc , svc . Namespace , svc . Name , endpointHostname ( addr , k . endpointNameMode ) } , "/" )
2017-08-23 07:19:41 +01:00
err = nil
services = append ( services , s )
2017-06-14 10:29:41 -04:00
}
}
}
}
continue
}
// ClusterIP service
2018-10-09 21:56:09 +01:00
for _ , p := range svc . Ports {
2022-02-09 09:25:10 -05:00
if ! ( matchPortAndProtocol ( r . port , p . Name , r . protocol , string ( p . Protocol ) ) ) {
2016-07-14 14:50:14 -07:00
continue
}
2017-06-14 10:29:41 -04:00
2017-08-23 07:19:41 +01:00
err = nil
2020-12-21 05:30:24 -05:00
for _ , ip := range svc . ClusterIPs {
s := msg . Service { Host : ip , Port : int ( p . Port ) , TTL : k . ttl }
s . Key = strings . Join ( [ ] string { zonePath , Svc , svc . Namespace , svc . Name } , "/" )
services = append ( services , s )
}
2017-08-23 07:19:41 +01:00
}
2016-07-14 14:50:14 -07:00
}
2017-08-23 07:19:41 +01:00
return services , err
2016-06-06 12:49:53 -07:00
}
2025-05-19 07:58:16 +02:00
// findMultiClusterServices returns the multicluster services matching r from the cache.
func ( k * Kubernetes ) findMultiClusterServices ( r recordRequest , zone string ) ( services [ ] msg . Service , err error ) {
if ! k . namespaceExposed ( r . namespace ) {
return nil , errNoItems
}
// handle empty service name
if r . service == "" {
if k . namespaceExposed ( r . namespace ) {
// NODATA
return nil , nil
}
// NXDOMAIN
return nil , errNoItems
}
err = errNoItems
var (
endpointsListFunc func ( ) [ ] * object . MultiClusterEndpoints
endpointsList [ ] * object . MultiClusterEndpoints
serviceList [ ] * object . ServiceImport
)
idx := object . ServiceImportKey ( r . service , r . namespace )
serviceList = k . APIConn . SvcImportIndex ( idx )
endpointsListFunc = func ( ) [ ] * object . MultiClusterEndpoints { return k . APIConn . McEpIndex ( idx ) }
zonePath := msg . Path ( zone , coredns )
for _ , svc := range serviceList {
if ! match ( r . namespace , svc . Namespace ) || ! match ( r . service , svc . Name ) {
continue
}
// If "ignore empty_service" option is set and no endpoints exist, return NXDOMAIN unless
// it's a headless or externalName service (covered below).
if k . opts . ignoreEmptyService && ! svc . Headless ( ) { // serve NXDOMAIN if no endpoint is able to answer
podsCount := 0
for _ , ep := range endpointsListFunc ( ) {
for _ , eps := range ep . Subsets {
podsCount += len ( eps . Addresses )
}
}
if podsCount == 0 {
continue
}
}
// Endpoint query or headless service
if svc . Headless ( ) || r . endpoint != "" {
if endpointsList == nil {
endpointsList = endpointsListFunc ( )
}
for _ , ep := range endpointsList {
if object . MultiClusterEndpointsKey ( svc . Name , svc . Namespace ) != ep . Index {
continue
}
for _ , eps := range ep . Subsets {
for _ , addr := range eps . Addresses {
// See comments in parse.go parseRequest about the endpoint handling.
if r . endpoint != "" {
if ! match ( r . cluster , ep . ClusterId ) || ! match ( r . endpoint , endpointHostname ( addr , k . endpointNameMode ) ) {
continue
}
}
for _ , p := range eps . Ports {
if ! ( matchPortAndProtocol ( r . port , p . Name , r . protocol , p . Protocol ) ) {
continue
}
s := msg . Service { Host : addr . IP , Port : int ( p . Port ) , TTL : k . ttl }
s . Key = strings . Join ( [ ] string { zonePath , Svc , svc . Namespace , svc . Name , ep . ClusterId , endpointHostname ( addr , k . endpointNameMode ) } , "/" )
err = nil
services = append ( services , s )
}
}
}
}
continue
}
// ClusterIP service
for _ , p := range svc . Ports {
if ! ( matchPortAndProtocol ( r . port , p . Name , r . protocol , string ( p . Protocol ) ) ) {
continue
}
err = nil
for _ , ip := range svc . ClusterIPs {
s := msg . Service { Host : ip , Port : int ( p . Port ) , TTL : k . ttl }
s . Key = strings . Join ( [ ] string { zonePath , Svc , svc . Namespace , svc . Name } , "/" )
services = append ( services , s )
}
}
}
return services , err
}
2020-09-24 11:30:39 -07:00
// Serial return the SOA serial.
2025-05-19 07:58:16 +02:00
func ( k * Kubernetes ) Serial ( state request . Request ) uint32 {
if ! k . isMultiClusterZone ( state . Zone ) {
return uint32 ( k . APIConn . Modified ( ModifiedInternal ) )
} else {
return uint32 ( k . APIConn . Modified ( ModifiedMultiCluster ) )
}
}
2020-09-24 11:30:39 -07:00
// MinTTL returns the minimal TTL.
func ( k * Kubernetes ) MinTTL ( state request . Request ) uint32 { return k . ttl }
2025-05-19 07:58:16 +02:00
func ( k * Kubernetes ) isMultiClusterZone ( zone string ) bool {
z := plugin . Zones ( k . opts . multiclusterZones ) . Matches ( zone )
return z != ""
}
2022-02-09 09:25:10 -05:00
// match checks if a and b are equal.
2017-08-22 20:44:42 +01:00
func match ( a , b string ) bool {
2017-08-05 12:29:43 -07:00
return strings . EqualFold ( a , b )
2016-07-14 14:50:14 -07:00
}
2022-08-30 22:35:31 +08:00
// matchPortAndProtocol matches port and protocol, permitting the 'a' inputs to be wild
2022-02-09 09:25:10 -05:00
func matchPortAndProtocol ( aPort , bPort , aProtocol , bProtocol string ) bool {
return ( match ( aPort , bPort ) || aPort == "" ) && ( match ( aProtocol , bProtocol ) || aProtocol == "" )
2017-08-22 20:44:42 +01:00
}
2018-12-08 13:37:00 +00:00
const coredns = "c" // used as a fake key prefix in msg.Service