mirror of
				https://github.com/coredns/coredns.git
				synced 2025-11-04 03:03:14 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			315 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			315 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package kubernetes
 | 
						|
 | 
						|
import (
 | 
						|
	"errors"
 | 
						|
	"flag"
 | 
						|
	"fmt"
 | 
						|
	"os"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/coredns/coredns/core/dnsserver"
 | 
						|
	"github.com/coredns/coredns/plugin"
 | 
						|
	"github.com/coredns/coredns/plugin/pkg/dnsutil"
 | 
						|
	clog "github.com/coredns/coredns/plugin/pkg/log"
 | 
						|
	"github.com/coredns/coredns/plugin/pkg/parse"
 | 
						|
	"github.com/coredns/coredns/plugin/pkg/upstream"
 | 
						|
 | 
						|
	"github.com/caddyserver/caddy"
 | 
						|
	"github.com/miekg/dns"
 | 
						|
	meta "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
 | 
						|
	// Pull this in for logtostderr flag parsing
 | 
						|
	"k8s.io/klog"
 | 
						|
 | 
						|
	// Excluding azure because it is failing to compile
 | 
						|
	// pull this in here, because we want it excluded if plugin.cfg doesn't have k8s
 | 
						|
	_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
 | 
						|
	// pull this in here, because we want it excluded if plugin.cfg doesn't have k8s
 | 
						|
	_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
 | 
						|
	// pull this in here, because we want it excluded if plugin.cfg doesn't have k8s
 | 
						|
	_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
 | 
						|
	"k8s.io/client-go/tools/clientcmd"
 | 
						|
)
 | 
						|
 | 
						|
var log = clog.NewWithPlugin("kubernetes")
 | 
						|
 | 
						|
func init() {
 | 
						|
	// Kubernetes plugin uses the kubernetes library, which now uses klog, we must set and parse this flag
 | 
						|
	// so we don't log to the filesystem, which can fill up and crash CoreDNS indirectly by calling os.Exit().
 | 
						|
	// We also set: os.Stderr = os.Stdout in the setup function below so we output to standard out; as we do for
 | 
						|
	// all CoreDNS logging. We can't do *that* in the init function, because we, when starting, also barf some
 | 
						|
	// things to stderr.
 | 
						|
	klogFlags := flag.NewFlagSet("klog", flag.ExitOnError)
 | 
						|
	klog.InitFlags(klogFlags)
 | 
						|
	logtostderr := klogFlags.Lookup("logtostderr")
 | 
						|
	logtostderr.Value.Set("true")
 | 
						|
 | 
						|
	caddy.RegisterPlugin("kubernetes", caddy.Plugin{
 | 
						|
		ServerType: "dns",
 | 
						|
		Action:     setup,
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func setup(c *caddy.Controller) error {
 | 
						|
	// See comment in the init function.
 | 
						|
	os.Stderr = os.Stdout
 | 
						|
 | 
						|
	k, err := kubernetesParse(c)
 | 
						|
	if err != nil {
 | 
						|
		return plugin.Error("kubernetes", err)
 | 
						|
	}
 | 
						|
 | 
						|
	err = k.InitKubeCache()
 | 
						|
	if err != nil {
 | 
						|
		return plugin.Error("kubernetes", err)
 | 
						|
	}
 | 
						|
 | 
						|
	k.RegisterKubeCache(c)
 | 
						|
 | 
						|
	dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
 | 
						|
		k.Next = next
 | 
						|
		return k
 | 
						|
	})
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// RegisterKubeCache registers KubeCache start and stop functions with Caddy
 | 
						|
func (k *Kubernetes) RegisterKubeCache(c *caddy.Controller) {
 | 
						|
	c.OnStartup(func() error {
 | 
						|
		go k.APIConn.Run()
 | 
						|
 | 
						|
		timeout := time.After(5 * time.Second)
 | 
						|
		ticker := time.NewTicker(100 * time.Millisecond)
 | 
						|
		for {
 | 
						|
			select {
 | 
						|
			case <-ticker.C:
 | 
						|
				if k.APIConn.HasSynced() {
 | 
						|
					return nil
 | 
						|
				}
 | 
						|
			case <-timeout:
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
	})
 | 
						|
 | 
						|
	c.OnShutdown(func() error {
 | 
						|
		return k.APIConn.Stop()
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
 | 
						|
	var (
 | 
						|
		k8s *Kubernetes
 | 
						|
		err error
 | 
						|
	)
 | 
						|
 | 
						|
	i := 0
 | 
						|
	for c.Next() {
 | 
						|
		if i > 0 {
 | 
						|
			return nil, plugin.ErrOnce
 | 
						|
		}
 | 
						|
		i++
 | 
						|
 | 
						|
		k8s, err = ParseStanza(c)
 | 
						|
		if err != nil {
 | 
						|
			return k8s, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return k8s, nil
 | 
						|
}
 | 
						|
 | 
						|
// ParseStanza parses a kubernetes stanza
 | 
						|
func ParseStanza(c *caddy.Controller) (*Kubernetes, error) {
 | 
						|
 | 
						|
	k8s := New([]string{""})
 | 
						|
	k8s.interfaceAddrsFunc = localPodIP
 | 
						|
	k8s.autoPathSearch = searchFromResolvConf()
 | 
						|
 | 
						|
	opts := dnsControlOpts{
 | 
						|
		initEndpointsCache: true,
 | 
						|
		ignoreEmptyService: false,
 | 
						|
	}
 | 
						|
	k8s.opts = opts
 | 
						|
 | 
						|
	zones := c.RemainingArgs()
 | 
						|
 | 
						|
	if len(zones) != 0 {
 | 
						|
		k8s.Zones = zones
 | 
						|
		for i := 0; i < len(k8s.Zones); i++ {
 | 
						|
			k8s.Zones[i] = plugin.Host(k8s.Zones[i]).Normalize()
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		k8s.Zones = make([]string, len(c.ServerBlockKeys))
 | 
						|
		for i := 0; i < len(c.ServerBlockKeys); i++ {
 | 
						|
			k8s.Zones[i] = plugin.Host(c.ServerBlockKeys[i]).Normalize()
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	k8s.primaryZoneIndex = -1
 | 
						|
	for i, z := range k8s.Zones {
 | 
						|
		if dnsutil.IsReverse(z) > 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		k8s.primaryZoneIndex = i
 | 
						|
		break
 | 
						|
	}
 | 
						|
 | 
						|
	if k8s.primaryZoneIndex == -1 {
 | 
						|
		return nil, errors.New("non-reverse zone name must be used")
 | 
						|
	}
 | 
						|
 | 
						|
	k8s.Upstream = upstream.New()
 | 
						|
 | 
						|
	for c.NextBlock() {
 | 
						|
		switch c.Val() {
 | 
						|
		case "endpoint_pod_names":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) > 0 {
 | 
						|
				return nil, c.ArgErr()
 | 
						|
			}
 | 
						|
			k8s.endpointNameMode = true
 | 
						|
			continue
 | 
						|
		case "pods":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) == 1 {
 | 
						|
				switch args[0] {
 | 
						|
				case podModeDisabled, podModeInsecure, podModeVerified:
 | 
						|
					k8s.podMode = args[0]
 | 
						|
				default:
 | 
						|
					return nil, fmt.Errorf("wrong value for pods: %s,  must be one of: disabled, verified, insecure", args[0])
 | 
						|
				}
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, c.ArgErr()
 | 
						|
		case "namespaces":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) > 0 {
 | 
						|
				for _, a := range args {
 | 
						|
					k8s.Namespaces[a] = struct{}{}
 | 
						|
				}
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, c.ArgErr()
 | 
						|
		case "endpoint":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) > 0 {
 | 
						|
				// Multiple endoints are deprecated but still could be specified,
 | 
						|
				// only the first one be used, though
 | 
						|
				k8s.APIServerList = args
 | 
						|
				if len(args) > 1 {
 | 
						|
					log.Warningf("Multiple endpoints have been deprecated, only the first specified endpoint '%s' is used", args[0])
 | 
						|
				}
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, c.ArgErr()
 | 
						|
		case "tls": // cert key cacertfile
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) == 3 {
 | 
						|
				k8s.APIClientCert, k8s.APIClientKey, k8s.APICertAuth = args[0], args[1], args[2]
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, c.ArgErr()
 | 
						|
		case "resyncperiod":
 | 
						|
			continue
 | 
						|
		case "labels":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) > 0 {
 | 
						|
				labelSelectorString := strings.Join(args, " ")
 | 
						|
				ls, err := meta.ParseToLabelSelector(labelSelectorString)
 | 
						|
				if err != nil {
 | 
						|
					return nil, fmt.Errorf("unable to parse label selector value: '%v': %v", labelSelectorString, err)
 | 
						|
				}
 | 
						|
				k8s.opts.labelSelector = ls
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, c.ArgErr()
 | 
						|
		case "namespace_labels":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) > 0 {
 | 
						|
				namespaceLabelSelectorString := strings.Join(args, " ")
 | 
						|
				nls, err := meta.ParseToLabelSelector(namespaceLabelSelectorString)
 | 
						|
				if err != nil {
 | 
						|
					return nil, fmt.Errorf("unable to parse namespace_label selector value: '%v': %v", namespaceLabelSelectorString, err)
 | 
						|
				}
 | 
						|
				k8s.opts.namespaceLabelSelector = nls
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, c.ArgErr()
 | 
						|
		case "fallthrough":
 | 
						|
			k8s.Fall.SetZonesFromArgs(c.RemainingArgs())
 | 
						|
		case "upstream":
 | 
						|
			// remove soon
 | 
						|
			c.RemainingArgs() // eat remaining args
 | 
						|
		case "ttl":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) == 0 {
 | 
						|
				return nil, c.ArgErr()
 | 
						|
			}
 | 
						|
			t, err := strconv.Atoi(args[0])
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			if t < 0 || t > 3600 {
 | 
						|
				return nil, c.Errf("ttl must be in range [0, 3600]: %d", t)
 | 
						|
			}
 | 
						|
			k8s.ttl = uint32(t)
 | 
						|
		case "transfer":
 | 
						|
			tos, froms, err := parse.Transfer(c, false)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			if len(froms) != 0 {
 | 
						|
				return nil, c.Errf("transfer from is not supported with this plugin")
 | 
						|
			}
 | 
						|
			k8s.TransferTo = tos
 | 
						|
		case "noendpoints":
 | 
						|
			if len(c.RemainingArgs()) != 0 {
 | 
						|
				return nil, c.ArgErr()
 | 
						|
			}
 | 
						|
			k8s.opts.initEndpointsCache = false
 | 
						|
		case "ignore":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) > 0 {
 | 
						|
				ignore := args[0]
 | 
						|
				if ignore == "empty_service" {
 | 
						|
					k8s.opts.ignoreEmptyService = true
 | 
						|
					continue
 | 
						|
				} else {
 | 
						|
					return nil, fmt.Errorf("unable to parse ignore value: '%v'", ignore)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		case "kubeconfig":
 | 
						|
			args := c.RemainingArgs()
 | 
						|
			if len(args) == 2 {
 | 
						|
				config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
 | 
						|
					&clientcmd.ClientConfigLoadingRules{ExplicitPath: args[0]},
 | 
						|
					&clientcmd.ConfigOverrides{CurrentContext: args[1]},
 | 
						|
				)
 | 
						|
				k8s.ClientConfig = config
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			return nil, c.ArgErr()
 | 
						|
		default:
 | 
						|
			return nil, c.Errf("unknown property '%s'", c.Val())
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if len(k8s.Namespaces) != 0 && k8s.opts.namespaceLabelSelector != nil {
 | 
						|
		return nil, c.Errf("namespaces and namespace_labels cannot both be set")
 | 
						|
	}
 | 
						|
 | 
						|
	return k8s, nil
 | 
						|
}
 | 
						|
 | 
						|
func searchFromResolvConf() []string {
 | 
						|
	rc, err := dns.ClientConfigFromFile("/etc/resolv.conf")
 | 
						|
	if err != nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	plugin.Zones(rc.Search).Normalize()
 | 
						|
	return rc.Search
 | 
						|
}
 |