2016-06-06 12:49:53 -07:00
// Package kubernetes provides the kubernetes backend.
package kubernetes
import (
2016-07-07 01:40:58 -07:00
"errors"
2016-10-30 15:54:16 +00:00
"fmt"
2016-07-18 10:47:36 -07:00
"log"
2016-11-05 07:57:08 -04:00
"strconv"
2016-09-23 09:48:11 -03:00
"strings"
2016-06-06 12:49:53 -07:00
"time"
"github.com/miekg/coredns/middleware"
2016-09-23 09:14:12 +01:00
"github.com/miekg/coredns/middleware/etcd/msg"
2016-07-07 01:40:58 -07:00
"github.com/miekg/coredns/middleware/kubernetes/nametemplate"
2016-09-07 11:10:16 +01:00
"github.com/miekg/coredns/middleware/pkg/dnsutil"
2016-10-30 15:54:16 +00:00
dnsstrings "github.com/miekg/coredns/middleware/pkg/strings"
2016-06-06 12:49:53 -07:00
"github.com/miekg/coredns/middleware/proxy"
2016-10-30 15:54:16 +00:00
"github.com/miekg/coredns/request"
2016-06-06 12:49:53 -07:00
2016-07-07 01:40:58 -07:00
"github.com/miekg/dns"
2016-11-05 15:43:27 +00:00
"k8s.io/client-go/1.5/kubernetes"
2016-11-05 07:57:08 -04:00
"k8s.io/client-go/1.5/pkg/api"
unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned"
2016-11-05 15:43:27 +00:00
"k8s.io/client-go/1.5/pkg/labels"
2016-11-05 07:57:08 -04:00
"k8s.io/client-go/1.5/rest"
"k8s.io/client-go/1.5/tools/clientcmd"
clientcmdapi "k8s.io/client-go/1.5/tools/clientcmd/api"
2016-08-05 18:19:51 -07:00
)
2016-09-23 09:14:12 +01:00
// Kubernetes implements a middleware that connects to a Kubernetes cluster.
2016-06-06 12:49:53 -07:00
type Kubernetes struct {
2016-08-12 20:44:08 -07:00
Next middleware . Handler
Zones [ ] string
Proxy proxy . Proxy // Proxy for looking up names during the resolution process
APIEndpoint string
2016-09-23 18:07:06 -04:00
APICertAuth string
APIClientCert string
APIClientKey string
2016-08-12 20:44:08 -07:00
APIConn * dnsController
ResyncPeriod time . Duration
2016-10-12 12:46:35 +01:00
NameTemplate * nametemplate . Template
2016-08-12 20:44:08 -07:00
Namespaces [ ] string
LabelSelector * unversionedapi . LabelSelector
2016-08-19 17:14:17 -07:00
Selector * labels . Selector
2016-06-06 12:49:53 -07:00
}
2016-11-10 16:24:06 -05:00
var noItemsErr = errors . New ( "no items found" )
var nsUnexposedErr = errors . New ( "namespace is not exposed" )
2016-10-30 15:54:16 +00:00
// Services implements the ServiceBackend interface.
func ( k * Kubernetes ) Services ( state request . Request , exact bool , opt middleware . Options ) ( [ ] msg . Service , [ ] msg . Service , error ) {
s , e := k . Records ( state . Name ( ) , exact )
return s , nil , e // Haven't implemented debug queries yet.
}
2016-11-05 15:43:27 +00:00
// Reverse implements the ServiceBackend interface.
func ( k * Kubernetes ) Reverse ( state request . Request , exact bool , opt middleware . Options ) ( [ ] msg . Service , [ ] msg . Service , error ) {
ip := dnsutil . ExtractAddressFromReverse ( state . Name ( ) )
if ip == "" {
return nil , nil , nil
}
records := k . getServiceRecordForIP ( ip , state . Name ( ) )
return records , nil , nil
}
2016-10-30 15:54:16 +00:00
// Lookup implements the ServiceBackend interface.
func ( k * Kubernetes ) Lookup ( state request . Request , name string , typ uint16 ) ( * dns . Msg , error ) {
return k . Proxy . Lookup ( state , name , typ )
}
// IsNameError implements the ServiceBackend interface.
func ( k * Kubernetes ) IsNameError ( err error ) bool {
2016-11-10 16:24:06 -05:00
return err == noItemsErr || err == nsUnexposedErr
2016-10-30 15:54:16 +00:00
}
// Debug implements the ServiceBackend interface.
func ( k * Kubernetes ) Debug ( ) string {
return "debug"
}
2016-11-05 07:57:08 -04:00
func ( k * Kubernetes ) getClientConfig ( ) ( * rest . Config , error ) {
2016-08-05 18:19:51 -07:00
// For a custom api server or running outside a k8s cluster
2016-08-08 14:30:04 -07:00
// set URL in env.KUBERNETES_MASTER or set endpoint in Corefile
2016-08-05 18:19:51 -07:00
loadingRules := clientcmd . NewDefaultClientConfigLoadingRules ( )
overrides := & clientcmd . ConfigOverrides { }
2016-09-23 18:07:06 -04:00
clusterinfo := clientcmdapi . Cluster { }
authinfo := clientcmdapi . AuthInfo { }
2016-08-22 23:15:21 -07:00
if len ( k . APIEndpoint ) > 0 {
2016-09-23 18:07:06 -04:00
clusterinfo . Server = k . APIEndpoint
2016-10-19 17:04:35 -04:00
} else {
2016-11-05 07:57:08 -04:00
cc , err := rest . InClusterConfig ( )
2016-10-19 17:04:35 -04:00
if err != nil {
return nil , err
}
return cc , err
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
}
overrides . ClusterInfo = clusterinfo
overrides . AuthInfo = authinfo
2016-08-05 18:19:51 -07:00
clientConfig := clientcmd . NewNonInteractiveDeferredLoadingClientConfig ( loadingRules , overrides )
2016-09-23 18:07:06 -04:00
return clientConfig . ClientConfig ( )
}
// InitKubeCache initializes a new Kubernetes cache.
func ( k * Kubernetes ) InitKubeCache ( ) error {
config , err := k . getClientConfig ( )
2016-08-05 18:19:51 -07:00
if err != nil {
return err
}
2016-11-05 07:57:08 -04:00
kubeClient , err := kubernetes . NewForConfig ( config )
2016-08-05 18:19:51 -07:00
if err != nil {
2016-10-30 15:54:16 +00:00
return fmt . Errorf ( "Failed to create kubernetes notification controller: %v" , err )
2016-08-05 18:19:51 -07:00
}
2016-10-30 15:54:16 +00:00
if k . LabelSelector != nil {
2016-08-19 17:14:17 -07:00
var selector labels . Selector
2016-08-22 23:15:21 -07:00
selector , err = unversionedapi . LabelSelectorAsSelector ( k . LabelSelector )
k . Selector = & selector
2016-08-19 17:14:17 -07:00
if err != nil {
2016-10-30 15:54:16 +00:00
return fmt . Errorf ( "Unable to create Selector for LabelSelector '%s'.Error was: %s" , k . LabelSelector , err )
2016-08-19 17:14:17 -07:00
}
2016-10-30 15:54:16 +00:00
}
if k . LabelSelector == nil {
log . Printf ( "[INFO] Kubernetes middleware configured without a label selector. No label-based filtering will be performed." )
} else {
2016-08-22 23:15:21 -07:00
log . Printf ( "[INFO] Kubernetes middleware configured with the label selector '%s'. Only kubernetes objects matching this label selector will be exposed." , unversionedapi . FormatLabelSelector ( k . LabelSelector ) )
2016-08-12 20:44:08 -07:00
}
2016-10-30 15:54:16 +00:00
2016-08-22 23:15:21 -07:00
k . APIConn = newdnsController ( kubeClient , k . ResyncPeriod , k . Selector )
2016-08-05 18:19:51 -07:00
return err
}
2016-07-07 01:40:58 -07:00
// getZoneForName returns the zone string that matches the name and a
// list of the DNS labels from name that are within the zone.
// For example, if "coredns.local" is a zone configured for the
// Kubernetes middleware, then getZoneForName("a.b.coredns.local")
// will return ("coredns.local", ["a", "b"]).
2016-08-22 23:15:21 -07:00
func ( k * Kubernetes ) getZoneForName ( name string ) ( string , [ ] string ) {
2016-07-07 01:40:58 -07:00
var zone string
var serviceSegments [ ] string
2016-06-06 12:49:53 -07:00
2016-08-22 23:15:21 -07:00
for _ , z := range k . Zones {
2016-07-07 01:40:58 -07:00
if dns . IsSubDomain ( z , name ) {
zone = z
serviceSegments = dns . SplitDomainName ( name )
serviceSegments = serviceSegments [ : len ( serviceSegments ) - dns . CountLabel ( zone ) ]
break
}
}
return zone , serviceSegments
}
2016-06-06 12:49:53 -07:00
2016-10-30 15:54:16 +00:00
// Records looks up services in kubernetes. If exact is true, it will lookup
// just this name. This is used when find matches when completing SRV lookups
2016-06-06 12:49:53 -07:00
// for instance.
2016-08-22 23:15:21 -07:00
func ( k * Kubernetes ) Records ( name string , exact bool ) ( [ ] msg . Service , error ) {
2016-07-07 01:40:58 -07:00
var (
serviceName string
namespace string
typeName string
)
2016-08-22 23:15:21 -07:00
zone , serviceSegments := k . getZoneForName ( name )
2016-07-07 01:40:58 -07:00
// TODO: Implementation above globbed together segments for the serviceName if
// multiple segments remained. Determine how to do similar globbing using
// the template-based implementation.
2016-10-12 12:46:35 +01:00
namespace = k . NameTemplate . NamespaceFromSegmentArray ( serviceSegments )
serviceName = k . NameTemplate . ServiceFromSegmentArray ( serviceSegments )
typeName = k . NameTemplate . TypeFromSegmentArray ( serviceSegments )
2016-07-07 01:40:58 -07:00
2016-07-14 14:50:14 -07:00
if namespace == "" {
err := errors . New ( "Parsing query string did not produce a namespace value. Assuming wildcard namespace." )
2016-07-18 10:47:36 -07:00
log . Printf ( "[WARN] %v\n" , err )
2016-09-23 09:48:11 -03:00
namespace = "*"
2016-07-14 14:50:14 -07:00
}
if serviceName == "" {
err := errors . New ( "Parsing query string did not produce a serviceName value. Assuming wildcard serviceName." )
2016-07-18 10:47:36 -07:00
log . Printf ( "[WARN] %v\n" , err )
2016-09-23 09:48:11 -03:00
serviceName = "*"
2016-07-14 14:50:14 -07:00
}
2016-09-23 09:48:11 -03:00
nsWildcard := symbolContainsWildcard ( namespace )
serviceWildcard := symbolContainsWildcard ( serviceName )
2016-06-06 12:49:53 -07:00
2016-07-14 14:50:14 -07:00
// Abort if the namespace does not contain a wildcard, and namespace is not published per CoreFile
// Case where namespace contains a wildcard is handled in Get(...) method.
2016-10-30 15:54:16 +00:00
if ( ! nsWildcard ) && ( len ( k . Namespaces ) > 0 ) && ( ! dnsstrings . StringInSlice ( namespace , k . Namespaces ) ) {
2016-11-10 16:24:06 -05:00
return nil , nsUnexposedErr
2016-07-07 01:40:58 -07:00
}
2016-06-06 12:49:53 -07:00
2016-11-05 07:57:08 -04:00
k8sItems , err := k . Get ( namespace , nsWildcard , serviceName , serviceWildcard , typeName )
2016-07-07 01:40:58 -07:00
if err != nil {
return nil , err
}
2016-11-10 16:24:06 -05:00
if len ( k8sItems ) == 0 {
2016-07-07 01:40:58 -07:00
// Did not find item in k8s
2016-11-10 16:24:06 -05:00
return nil , noItemsErr
2016-07-07 01:40:58 -07:00
}
2016-06-06 12:49:53 -07:00
2016-11-05 07:57:08 -04:00
records := k . getRecordsForServiceItems ( k8sItems , zone )
2016-07-07 01:40:58 -07:00
return records , nil
}
2016-06-06 12:49:53 -07:00
2016-11-05 07:57:08 -04:00
func ( k * Kubernetes ) getRecordsForServiceItems ( serviceItems [ ] * api . Service , zone string ) [ ] msg . Service {
2016-07-07 01:40:58 -07:00
var records [ ] msg . Service
2016-06-06 12:49:53 -07:00
2016-07-07 01:40:58 -07:00
for _ , item := range serviceItems {
clusterIP := item . Spec . ClusterIP
2016-07-14 14:50:14 -07:00
// Create records for each exposed port...
2016-11-05 07:57:08 -04:00
key := k . NameTemplate . RecordNameFromNameValues ( nametemplate . NameValues { TypeName : "svc" , ServiceName : item . ObjectMeta . Name , Namespace : item . ObjectMeta . Namespace , Zone : zone } )
key = strings . Replace ( key , "." , "/" , - 1 )
for i , p := range item . Spec . Ports {
2016-11-05 15:43:27 +00:00
s := msg . Service { Key : msg . Path ( strconv . Itoa ( i ) + "." + key , "coredns" ) , Host : clusterIP , Port : int ( p . Port ) }
2016-07-07 01:40:58 -07:00
records = append ( records , s )
}
}
2016-06-06 12:49:53 -07:00
2016-07-07 01:40:58 -07:00
return records
2016-06-06 12:49:53 -07:00
}
// Get performs the call to the Kubernetes http API.
2016-11-05 07:57:08 -04:00
func ( k * Kubernetes ) Get ( namespace string , nsWildcard bool , servicename string , serviceWildcard bool , typeName string ) ( [ ] * api . Service , error ) {
switch {
case typeName == "pod" :
return nil , fmt . Errorf ( "pod not implemented" )
default :
return k . getServices ( namespace , nsWildcard , servicename , serviceWildcard )
}
}
func ( k * Kubernetes ) getServices ( namespace string , nsWildcard bool , servicename string , serviceWildcard bool ) ( [ ] * api . Service , error ) {
2016-10-12 12:46:35 +01:00
serviceList := k . APIConn . ServiceList ( )
2016-06-06 12:49:53 -07:00
2016-09-23 10:13:02 -03:00
var resultItems [ ] * api . Service
2016-07-14 14:50:14 -07:00
2016-09-23 10:13:02 -03:00
for _ , item := range serviceList {
2016-08-05 18:19:51 -07:00
if symbolMatches ( namespace , item . Namespace , nsWildcard ) && symbolMatches ( servicename , item . Name , serviceWildcard ) {
2016-07-14 14:50:14 -07:00
// If namespace has a wildcard, filter results against Corefile namespace list.
// (Namespaces without a wildcard were filtered before the call to this function.)
2016-10-30 15:54:16 +00:00
if nsWildcard && ( len ( k . Namespaces ) > 0 ) && ( ! dnsstrings . StringInSlice ( item . Namespace , k . Namespaces ) ) {
2016-07-14 14:50:14 -07:00
continue
}
2016-09-22 08:29:50 -03:00
resultItems = append ( resultItems , item )
2016-07-14 14:50:14 -07:00
}
}
return resultItems , nil
2016-06-06 12:49:53 -07:00
}
2016-07-14 14:50:14 -07:00
func symbolMatches ( queryString string , candidateString string , wildcard bool ) bool {
result := false
switch {
case ! wildcard :
result = ( queryString == candidateString )
2016-09-23 09:48:11 -03:00
case queryString == "*" :
2016-07-14 14:50:14 -07:00
result = true
2016-09-23 09:48:11 -03:00
case queryString == "any" :
2016-07-14 14:50:14 -07:00
result = true
}
return result
}
2016-08-22 23:15:21 -07:00
func ( k * Kubernetes ) getServiceRecordForIP ( ip , name string ) [ ] msg . Service {
2016-09-23 10:13:02 -03:00
svcList , err := k . APIConn . svcLister . List ( labels . Everything ( ) )
2016-08-05 18:19:51 -07:00
if err != nil {
return nil
2016-06-06 12:49:53 -07:00
}
2016-09-23 10:13:02 -03:00
for _ , service := range svcList {
2016-08-05 18:19:51 -07:00
if service . Spec . ClusterIP == ip {
return [ ] msg . Service { msg . Service { Host : ip } }
}
2016-06-06 12:49:53 -07:00
}
2016-08-05 18:19:51 -07:00
return nil
2016-06-06 12:49:53 -07:00
}
2016-09-23 09:48:11 -03:00
// symbolContainsWildcard checks whether symbol contains a wildcard value
func symbolContainsWildcard ( symbol string ) bool {
return ( strings . Contains ( symbol , "*" ) || ( symbol == "any" ) )
}