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-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
2016-11-14 19:31:08 +00:00
primaryZone int
2016-08-12 20:44:08 -07:00
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
}
2017-01-05 10:09:59 -05:00
type endpoint struct {
addr api . EndpointAddress
port api . EndpointPort
}
type service struct {
name string
namespace string
addr string
ports [ ] api . ServicePort
endpoints [ ] endpoint
}
2016-11-11 16:56:15 +00:00
var errNoItems = errors . New ( "no items found" )
var errNsNotExposed = errors . New ( "namespace is not exposed" )
2017-01-05 10:09:59 -05:00
var errInvalidRequest = errors . New ( "invalid query name" )
2016-11-10 16:24:06 -05:00
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 ) {
2017-01-05 10:09:59 -05:00
if state . Type ( ) == "SRV" && ! ValidSRV ( state . Name ( ) ) {
return nil , nil , errInvalidRequest
}
2016-10-30 15:54:16 +00:00
s , e := k . Records ( state . Name ( ) , exact )
return s , nil , e // Haven't implemented debug queries yet.
}
2016-11-14 19:31:08 +00:00
// PrimaryZone will return the first non-reverse zone being handled by this middleware
2016-12-02 17:50:01 -05:00
func ( k * Kubernetes ) PrimaryZone ( ) string {
2016-11-14 19:31:08 +00:00
return k . Zones [ k . primaryZone ]
}
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 {
2017-01-05 10:09:59 -05:00
return err == errNoItems || err == errNsNotExposed || err == errInvalidRequest
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
2017-01-05 10:09:59 -05:00
// stripSRVPrefix separates out the port and protocol segments, if present
// If not present, assume all ports/protocols (e.g. wildcard)
func stripSRVPrefix ( name [ ] string ) ( string , string , [ ] string ) {
if name [ 0 ] [ 0 ] == '_' && name [ 1 ] [ 0 ] == '_' {
return name [ 0 ] [ 1 : ] , name [ 1 ] [ 1 : ] , name [ 2 : ]
}
// no srv prefix present
return "*" , "*" , name
}
func stripEndpointName ( name [ ] string ) ( endpoint string , nameOut [ ] string ) {
if len ( name ) == 4 {
return strings . ToLower ( name [ 0 ] ) , name [ 1 : ]
}
return "" , name
}
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 )
2017-01-05 10:09:59 -05:00
port , protocol , serviceSegments := stripSRVPrefix ( serviceSegments )
endpointname , serviceSegments := stripEndpointName ( serviceSegments )
2016-12-14 16:17:14 +00:00
if len ( serviceSegments ) < 3 {
return nil , errNoItems
}
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
}
// 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.
2017-01-05 10:09:59 -05:00
if ( ! symbolContainsWildcard ( namespace ) ) && ( len ( k . Namespaces ) > 0 ) && ( ! dnsstrings . StringInSlice ( namespace , k . Namespaces ) ) {
2016-11-11 16:56:15 +00:00
return nil , errNsNotExposed
2016-07-07 01:40:58 -07:00
}
2016-06-06 12:49:53 -07:00
2017-01-05 10:09:59 -05:00
k8sItems , err := k . Get ( namespace , serviceName , endpointname , port , protocol , 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-11 16:56:15 +00:00
return nil , errNoItems
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
2017-01-05 10:09:59 -05:00
func endpointHostname ( addr api . EndpointAddress ) string {
if addr . Hostname != "" {
return strings . ToLower ( addr . Hostname )
}
if strings . Contains ( addr . IP , "." ) {
return strings . Replace ( addr . IP , "." , "-" , - 1 )
}
if strings . Contains ( addr . IP , ":" ) {
return strings . ToLower ( strings . Replace ( addr . IP , ":" , "-" , - 1 ) )
}
return ""
}
func ( k * Kubernetes ) getRecordsForServiceItems ( serviceItems [ ] 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
2017-01-05 10:09:59 -05:00
for _ , svc := range serviceItems {
key := svc . name + "." + svc . namespace + ".svc." + zone
if svc . addr == api . ClusterIPNone {
// This is a headless service, create records for each endpoint
for _ , ep := range svc . endpoints {
ephostname := endpointHostname ( ep . addr )
s := msg . Service {
Key : msg . Path ( strings . ToLower ( ephostname + "." + key ) , "coredns" ) ,
Host : ep . addr . IP , Port : int ( ep . port . Port ) ,
2016-12-02 17:50:01 -05:00
}
2017-01-05 10:09:59 -05:00
records = append ( records , s )
2016-12-02 17:50:01 -05:00
}
} else {
// Create records for each exposed port...
2017-01-05 10:09:59 -05:00
for _ , p := range svc . ports {
s := msg . Service { Key : msg . Path ( strings . ToLower ( key ) , "coredns" ) , Host : svc . addr , Port : int ( p . Port ) }
2016-12-02 17:50:01 -05:00
records = append ( records , s )
}
2016-07-07 01:40:58 -07:00
}
}
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.
2017-01-05 10:09:59 -05:00
func ( k * Kubernetes ) Get ( namespace , servicename , endpointname , port , protocol , typeName string ) ( services [ ] service , err error ) {
2016-11-05 07:57:08 -04:00
switch {
case typeName == "pod" :
2017-01-05 10:09:59 -05:00
return nil , fmt . Errorf ( "%v not implemented" , typeName )
2016-11-05 07:57:08 -04:00
default :
2017-01-05 10:09:59 -05:00
return k . getServices ( namespace , servicename , endpointname , port , protocol )
2016-11-05 07:57:08 -04:00
}
}
2017-01-05 10:09:59 -05:00
func ( k * Kubernetes ) getServices ( namespace , servicename , endpointname , port , protocol string ) ( [ ] service , error ) {
2016-10-12 12:46:35 +01:00
serviceList := k . APIConn . ServiceList ( )
2016-06-06 12:49:53 -07:00
2017-01-05 10:09:59 -05:00
var resultItems [ ] service
2016-07-14 14:50:14 -07:00
2017-01-05 10:09:59 -05:00
nsWildcard := symbolContainsWildcard ( namespace )
serviceWildcard := symbolContainsWildcard ( servicename )
portWildcard := symbolContainsWildcard ( port )
protocolWildcard := symbolContainsWildcard ( protocol )
for _ , svc := range serviceList {
if ! ( symbolMatches ( namespace , svc . Namespace , nsWildcard ) && symbolMatches ( servicename , svc . Name , serviceWildcard ) ) {
continue
}
// If namespace has a wildcard, filter results against Corefile namespace list.
// (Namespaces without a wildcard were filtered before the call to this function.)
if nsWildcard && ( len ( k . Namespaces ) > 0 ) && ( ! dnsstrings . StringInSlice ( svc . Namespace , k . Namespaces ) ) {
continue
}
s := service { name : svc . Name , namespace : svc . Namespace , addr : svc . Spec . ClusterIP }
if s . addr != api . ClusterIPNone {
for _ , p := range svc . Spec . Ports {
if ! ( symbolMatches ( port , strings . ToLower ( p . Name ) , portWildcard ) && symbolMatches ( protocol , strings . ToLower ( string ( p . Protocol ) ) , protocolWildcard ) ) {
continue
}
s . ports = append ( s . ports , p )
}
resultItems = append ( resultItems , s )
continue
}
// Headless service
endpointsList , err := k . APIConn . epLister . List ( )
if err != nil {
continue
}
for _ , ep := range endpointsList . Items {
if ep . ObjectMeta . Name != svc . Name || ep . ObjectMeta . Namespace != svc . Namespace {
2016-07-14 14:50:14 -07:00
continue
}
2017-01-05 10:09:59 -05:00
for _ , eps := range ep . Subsets {
for _ , addr := range eps . Addresses {
for _ , p := range eps . Ports {
ephostname := endpointHostname ( addr )
if endpointname != "" && endpointname != ephostname {
continue
}
if ! ( symbolMatches ( port , strings . ToLower ( p . Name ) , portWildcard ) && symbolMatches ( protocol , strings . ToLower ( string ( p . Protocol ) ) , protocolWildcard ) ) {
continue
}
s . endpoints = append ( s . endpoints , endpoint { addr : addr , port : p } )
}
}
}
2016-07-14 14:50:14 -07:00
}
2017-01-05 10:09:59 -05:00
resultItems = append ( resultItems , s )
2016-07-14 14:50:14 -07:00
}
return resultItems , nil
2016-06-06 12:49:53 -07:00
}
2017-01-05 10:09:59 -05:00
func symbolMatches ( queryString , candidateString string , wildcard bool ) bool {
2016-07-14 14:50:14 -07:00
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
}
2017-01-05 10:09:59 -05:00
// getServiceRecordForIP: Gets a service record with a cluster ip matching the ip argument
// If a service cluster ip does not match, it checks all endpoints
2016-08-22 23:15:21 -07:00
func ( k * Kubernetes ) getServiceRecordForIP ( ip , name string ) [ ] msg . Service {
2017-01-05 10:09:59 -05:00
// First check services with cluster ips
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 {
2017-01-05 10:09:59 -05:00
if ! dnsstrings . StringInSlice ( service . Namespace , k . Namespaces ) {
continue
}
2016-08-05 18:19:51 -07:00
if service . Spec . ClusterIP == ip {
2017-01-05 10:09:59 -05:00
domain := service . Name + "." + service . Namespace + ".svc." + k . PrimaryZone ( )
return [ ] msg . Service { msg . Service { Host : domain } }
}
}
// If no cluster ips match, search endpoints
epList , err := k . APIConn . epLister . List ( )
if err != nil {
return nil
}
for _ , ep := range epList . Items {
if ! dnsstrings . StringInSlice ( ep . ObjectMeta . Namespace , k . Namespaces ) {
continue
}
for _ , eps := range ep . Subsets {
for _ , addr := range eps . Addresses {
if addr . IP == ip {
domain := endpointHostname ( addr ) + "." + ep . ObjectMeta . Name + "." + ep . ObjectMeta . Namespace + ".svc." + k . PrimaryZone ( )
return [ ] msg . Service { msg . Service { Host : domain } }
}
}
2016-08-05 18:19:51 -07:00
}
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" ) )
}
2017-01-05 10:09:59 -05:00
// ValidSRV parses a server record validating _port._proto. prefix labels.
// The valid schema is:
// * Fist two segments must start with an "_",
// * Second segment must be one of _tcp|_udp|_*|_any
func ValidSRV ( name string ) bool {
// Does it start with a "_" ?
if len ( name ) > 0 && name [ 0 ] != '_' {
return false
}
// First label
first , end := dns . NextLabel ( name , 0 )
if end {
return false
}
// Second label
off , end := dns . NextLabel ( name , first )
if end {
return false
}
// first:off has captured _tcp. or _udp. (if present)
second := name [ first : off ]
if len ( second ) > 0 && second [ 0 ] != '_' {
return false
}
// A bit convoluted to avoid strings.ToLower
if len ( second ) == 5 {
// matches _tcp
if ( second [ 1 ] == 't' || second [ 1 ] == 'T' ) && ( second [ 2 ] == 'c' || second [ 2 ] == 'C' ) &&
( second [ 3 ] == 'p' || second [ 3 ] == 'P' ) {
return true
}
// matches _udp
if ( second [ 1 ] == 'u' || second [ 1 ] == 'U' ) && ( second [ 2 ] == 'd' || second [ 2 ] == 'D' ) &&
( second [ 3 ] == 'p' || second [ 3 ] == 'P' ) {
return true
}
// matches _any
if ( second [ 1 ] == 'a' || second [ 1 ] == 'A' ) && ( second [ 2 ] == 'n' || second [ 2 ] == 'N' ) &&
( second [ 3 ] == 'y' || second [ 3 ] == 'Y' ) {
return true
}
}
// matches _*
if len ( second ) == 3 && second [ 1 ] == '*' {
return true
}
return false
}