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-07-18 10:47:36 -07:00
"log"
2016-06-06 12:49:53 -07:00
"time"
"github.com/miekg/coredns/middleware"
2016-07-07 01:40:58 -07:00
"github.com/miekg/coredns/middleware/kubernetes/msg"
"github.com/miekg/coredns/middleware/kubernetes/nametemplate"
"github.com/miekg/coredns/middleware/kubernetes/util"
2016-09-07 11:10:16 +01:00
"github.com/miekg/coredns/middleware/pkg/dnsutil"
2016-06-06 12:49:53 -07:00
"github.com/miekg/coredns/middleware/proxy"
2016-07-07 01:40:58 -07:00
"github.com/miekg/dns"
2016-08-05 18:19:51 -07:00
"k8s.io/kubernetes/pkg/api"
2016-08-12 20:44:08 -07:00
unversionedapi "k8s.io/kubernetes/pkg/api/unversioned"
unversionedclient "k8s.io/kubernetes/pkg/client/unversioned"
2016-08-05 18:19:51 -07:00
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
2016-08-19 17:14:17 -07:00
"k8s.io/kubernetes/pkg/labels"
2016-08-05 18:19:51 -07:00
)
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
APIConn * dnsController
ResyncPeriod time . Duration
NameTemplate * nametemplate . NameTemplate
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-08-22 23:15:21 -07:00
func ( k * Kubernetes ) InitKubeCache ( ) 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-08-22 23:15:21 -07:00
if len ( k . APIEndpoint ) > 0 {
overrides . ClusterInfo = clientcmdapi . Cluster { Server : k . APIEndpoint }
2016-08-05 18:19:51 -07:00
}
clientConfig := clientcmd . NewNonInteractiveDeferredLoadingClientConfig ( loadingRules , overrides )
config , err := clientConfig . ClientConfig ( )
if err != nil {
return err
}
2016-08-12 20:44:08 -07:00
kubeClient , err := unversionedclient . New ( config )
2016-08-05 18:19:51 -07:00
if err != nil {
log . Printf ( "[ERROR] Failed to create kubernetes notification controller: %v" , err )
return err
}
2016-08-22 23:15:21 -07:00
if k . LabelSelector == nil {
2016-08-16 09:12:52 -07:00
log . Printf ( "[INFO] Kubernetes middleware configured without a label selector. No label-based filtering will be performed." )
2016-08-12 20:44:08 -07:00
} else {
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-08-22 23:15:21 -07:00
log . Printf ( "[ERROR] Unable to create Selector for LabelSelector '%s'.Error was: %s" , k . LabelSelector , err )
2016-08-19 17:14:17 -07:00
return err
}
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-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
// 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
// for instance.
2016-08-22 23:15:21 -07:00
func ( k * Kubernetes ) Records ( name string , exact bool ) ( [ ] msg . Service , error ) {
2016-08-05 18:19:51 -07:00
// TODO: refector this.
// Right now GetNamespaceFromSegmentArray do not supports PRE queries
2016-09-07 11:10:16 +01:00
ip := dnsutil . ExtractAddressFromReverse ( name )
if ip != "" {
2016-08-22 23:15:21 -07:00
records := k . getServiceRecordForIP ( ip , name )
2016-08-05 18:19:51 -07:00
return records , nil
}
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-08-22 23:15:21 -07:00
namespace = k . NameTemplate . GetNamespaceFromSegmentArray ( serviceSegments )
serviceName = k . NameTemplate . GetServiceFromSegmentArray ( serviceSegments )
typeName = k . NameTemplate . GetTypeFromSegmentArray ( 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-07-14 14:50:14 -07:00
namespace = util . WildcardStar
}
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-07-14 14:50:14 -07:00
serviceName = util . WildcardStar
}
nsWildcard := util . SymbolContainsWildcard ( namespace )
serviceWildcard := util . 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-08-22 23:15:21 -07:00
if ( ! nsWildcard ) && ( len ( k . Namespaces ) > 0 ) && ( ! util . StringInSlice ( namespace , k . Namespaces ) ) {
2016-07-07 01:40:58 -07:00
return nil , nil
}
2016-06-06 12:49:53 -07:00
2016-08-22 23:15:21 -07:00
k8sItems , err := k . Get ( namespace , nsWildcard , serviceName , serviceWildcard )
2016-07-07 01:40:58 -07:00
if err != nil {
return nil , err
}
if k8sItems == nil {
// Did not find item in k8s
return nil , nil
}
2016-06-06 12:49:53 -07:00
2016-08-22 23:15:21 -07:00
records := k . getRecordsForServiceItems ( k8sItems , nametemplate . NameValues { TypeName : typeName , ServiceName : serviceName , Namespace : namespace , Zone : zone } )
2016-07-07 01:40:58 -07:00
return records , nil
}
2016-06-06 12:49:53 -07:00
2016-07-07 01:40:58 -07:00
// TODO: assemble name from parts found in k8s data based on name template rather than reusing query string
2016-08-22 23:15:21 -07:00
func ( k * Kubernetes ) getRecordsForServiceItems ( serviceItems [ ] api . Service , values nametemplate . NameValues ) [ ] 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 by constructing record name from template...
//values.Namespace = item.Metadata.Namespace
//values.ServiceName = item.Metadata.Name
//s := msg.Service{Host: g.NameTemplate.GetRecordNameFromNameValues(values)}
//records = append(records, s)
2016-06-06 12:49:53 -07:00
2016-07-14 14:50:14 -07:00
// Create records for each exposed port...
2016-07-07 01:40:58 -07:00
for _ , p := range item . Spec . Ports {
2016-08-05 18:19:51 -07:00
s := msg . Service { 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-08-22 23:15:21 -07:00
func ( k * Kubernetes ) Get ( namespace string , nsWildcard bool , servicename string , serviceWildcard bool ) ( [ ] api . Service , error ) {
serviceList := k . APIConn . GetServiceList ( )
2016-06-06 12:49:53 -07:00
2016-08-05 18:19:51 -07:00
var resultItems [ ] api . Service
2016-07-14 14:50:14 -07:00
for _ , item := range serviceList . Items {
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-08-22 23:15:21 -07:00
if nsWildcard && ( len ( k . Namespaces ) > 0 ) && ( ! util . StringInSlice ( item . Namespace , k . Namespaces ) ) {
2016-07-14 14:50:14 -07:00
continue
}
resultItems = append ( resultItems , item )
}
}
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 )
case queryString == util . WildcardStar :
result = true
case queryString == util . WildcardAny :
result = true
}
return result
}
2016-08-05 18:19:51 -07:00
// kubernetesNameError checks if the error is ErrorCodeKeyNotFound from kubernetes.
func isKubernetesNameError ( err error ) bool {
return false
2016-06-06 12:49:53 -07:00
}
2016-08-22 23:15:21 -07:00
func ( k * Kubernetes ) getServiceRecordForIP ( ip , name string ) [ ] msg . Service {
svcList , err := k . APIConn . svcLister . List ( )
2016-08-05 18:19:51 -07:00
if err != nil {
return nil
2016-06-06 12:49:53 -07:00
}
2016-08-05 18:19:51 -07:00
for _ , service := range svcList . Items {
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
}
const (
2016-07-07 01:40:58 -07:00
priority = 10 // default priority when nothing is set
ttl = 300 // default ttl when nothing is set
minTtl = 60
hostmaster = "hostmaster"
2016-06-06 12:49:53 -07:00
k8sTimeout = 5 * time . Second
)