2017-11-14 02:26:06 +00:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package dns
import (
"fmt"
"io"
"io/ioutil"
"net"
"os"
"path/filepath"
"strings"
"k8s.io/api/core/v1"
2018-08-31 06:29:58 +00:00
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2017-11-14 02:26:06 +00:00
"k8s.io/client-go/tools/record"
2017-11-19 03:46:24 +00:00
"k8s.io/kubernetes/pkg/apis/core/validation"
2018-02-06 22:11:09 +00:00
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
2017-11-14 02:26:06 +00:00
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/util/format"
2018-11-09 18:49:10 +00:00
"k8s.io/klog"
2017-11-14 02:26:06 +00:00
)
2017-11-17 02:44:13 +00:00
var (
// The default dns opt strings.
defaultDNSOptions = [ ] string { "ndots:5" }
)
2017-11-19 03:46:24 +00:00
type podDNSType int
const (
podDNSCluster podDNSType = iota
podDNSHost
podDNSNone
)
2017-11-14 02:26:06 +00:00
// Configurer is used for setting up DNS resolver configuration when launching pods.
type Configurer struct {
recorder record . EventRecorder
nodeRef * v1 . ObjectReference
nodeIP net . IP
// If non-nil, use this for container DNS server.
clusterDNS [ ] net . IP
// If non-empty, use this for container DNS search.
ClusterDomain string
// The path to the DNS resolver configuration file used as the base to generate
// the container's DNS resolver configuration file. This can be used in
// conjunction with clusterDomain and clusterDNS.
ResolverConfig string
}
// NewConfigurer returns a DNS configurer for launching pods.
func NewConfigurer ( recorder record . EventRecorder , nodeRef * v1 . ObjectReference , nodeIP net . IP , clusterDNS [ ] net . IP , clusterDomain , resolverConfig string ) * Configurer {
return & Configurer {
recorder : recorder ,
nodeRef : nodeRef ,
nodeIP : nodeIP ,
clusterDNS : clusterDNS ,
ClusterDomain : clusterDomain ,
ResolverConfig : resolverConfig ,
}
}
2017-11-19 03:46:24 +00:00
func omitDuplicates ( strs [ ] string ) [ ] string {
uniqueStrs := make ( map [ string ] bool )
2017-11-14 02:26:06 +00:00
2017-11-19 03:46:24 +00:00
var ret [ ] string
for _ , str := range strs {
if ! uniqueStrs [ str ] {
ret = append ( ret , str )
uniqueStrs [ str ] = true
2017-11-14 02:26:06 +00:00
}
}
2017-11-19 03:46:24 +00:00
return ret
2017-11-14 02:26:06 +00:00
}
2017-11-19 03:46:24 +00:00
func ( c * Configurer ) formDNSSearchFitsLimits ( composedSearch [ ] string , pod * v1 . Pod ) [ ] string {
2017-11-14 02:26:06 +00:00
limitsExceeded := false
2017-11-19 03:46:24 +00:00
if len ( composedSearch ) > validation . MaxDNSSearchPaths {
composedSearch = composedSearch [ : validation . MaxDNSSearchPaths ]
2017-11-14 02:26:06 +00:00
limitsExceeded = true
}
2017-11-19 03:46:24 +00:00
if resolvSearchLineStrLen := len ( strings . Join ( composedSearch , " " ) ) ; resolvSearchLineStrLen > validation . MaxDNSSearchListChars {
2017-11-14 02:26:06 +00:00
cutDomainsNum := 0
2017-11-19 03:46:24 +00:00
cutDomainsLen := 0
2017-11-14 02:26:06 +00:00
for i := len ( composedSearch ) - 1 ; i >= 0 ; i -- {
2017-11-19 03:46:24 +00:00
cutDomainsLen += len ( composedSearch [ i ] ) + 1
2017-11-14 02:26:06 +00:00
cutDomainsNum ++
2017-11-19 03:46:24 +00:00
if ( resolvSearchLineStrLen - cutDomainsLen ) <= validation . MaxDNSSearchListChars {
2017-11-14 02:26:06 +00:00
break
}
}
composedSearch = composedSearch [ : ( len ( composedSearch ) - cutDomainsNum ) ]
limitsExceeded = true
}
if limitsExceeded {
2017-11-19 03:46:24 +00:00
log := fmt . Sprintf ( "Search Line limits were exceeded, some search paths have been omitted, the applied search line is: %s" , strings . Join ( composedSearch , " " ) )
c . recorder . Event ( pod , v1 . EventTypeWarning , "DNSConfigForming" , log )
2018-11-09 18:49:10 +00:00
klog . Error ( log )
2017-11-14 02:26:06 +00:00
}
return composedSearch
}
2017-11-19 03:46:24 +00:00
func ( c * Configurer ) formDNSNameserversFitsLimits ( nameservers [ ] string , pod * v1 . Pod ) [ ] string {
if len ( nameservers ) > validation . MaxDNSNameservers {
nameservers = nameservers [ 0 : validation . MaxDNSNameservers ]
log := fmt . Sprintf ( "Nameserver limits were exceeded, some nameservers have been omitted, the applied nameserver line is: %s" , strings . Join ( nameservers , " " ) )
c . recorder . Event ( pod , v1 . EventTypeWarning , "DNSConfigForming" , log )
2018-11-09 18:49:10 +00:00
klog . Error ( log )
2017-11-19 03:46:24 +00:00
}
return nameservers
2017-11-14 02:26:06 +00:00
}
2017-11-19 03:46:24 +00:00
func ( c * Configurer ) formDNSConfigFitsLimits ( dnsConfig * runtimeapi . DNSConfig , pod * v1 . Pod ) * runtimeapi . DNSConfig {
dnsConfig . Servers = c . formDNSNameserversFitsLimits ( dnsConfig . Servers , pod )
dnsConfig . Searches = c . formDNSSearchFitsLimits ( dnsConfig . Searches , pod )
return dnsConfig
}
func ( c * Configurer ) generateSearchesForDNSClusterFirst ( hostSearch [ ] string , pod * v1 . Pod ) [ ] string {
2017-11-14 02:26:06 +00:00
if c . ClusterDomain == "" {
return hostSearch
}
nsSvcDomain := fmt . Sprintf ( "%s.svc.%s" , pod . Namespace , c . ClusterDomain )
svcDomain := fmt . Sprintf ( "svc.%s" , c . ClusterDomain )
2017-11-19 03:46:24 +00:00
clusterSearch := [ ] string { nsSvcDomain , svcDomain , c . ClusterDomain }
2017-11-14 02:26:06 +00:00
2017-11-19 03:46:24 +00:00
return omitDuplicates ( append ( clusterSearch , hostSearch ... ) )
2017-11-14 02:26:06 +00:00
}
// CheckLimitsForResolvConf checks limits in resolv.conf.
func ( c * Configurer ) CheckLimitsForResolvConf ( ) {
f , err := os . Open ( c . ResolverConfig )
if err != nil {
c . recorder . Event ( c . nodeRef , v1 . EventTypeWarning , "CheckLimitsForResolvConf" , err . Error ( ) )
2018-11-09 18:49:10 +00:00
klog . V ( 4 ) . Infof ( "CheckLimitsForResolvConf: " + err . Error ( ) )
2017-11-14 02:26:06 +00:00
return
}
defer f . Close ( )
_ , hostSearch , _ , err := parseResolvConf ( f )
if err != nil {
c . recorder . Event ( c . nodeRef , v1 . EventTypeWarning , "CheckLimitsForResolvConf" , err . Error ( ) )
2018-11-09 18:49:10 +00:00
klog . V ( 4 ) . Infof ( "CheckLimitsForResolvConf: " + err . Error ( ) )
2017-11-14 02:26:06 +00:00
return
}
2017-11-19 03:46:24 +00:00
domainCountLimit := validation . MaxDNSSearchPaths
2017-11-14 02:26:06 +00:00
if c . ClusterDomain != "" {
2017-11-19 03:46:24 +00:00
domainCountLimit -= 3
2017-11-14 02:26:06 +00:00
}
2017-11-19 03:46:24 +00:00
if len ( hostSearch ) > domainCountLimit {
log := fmt . Sprintf ( "Resolv.conf file '%s' contains search line consisting of more than %d domains!" , c . ResolverConfig , domainCountLimit )
2017-11-14 02:26:06 +00:00
c . recorder . Event ( c . nodeRef , v1 . EventTypeWarning , "CheckLimitsForResolvConf" , log )
2018-11-09 18:49:10 +00:00
klog . V ( 4 ) . Infof ( "CheckLimitsForResolvConf: " + log )
2017-11-14 02:26:06 +00:00
return
}
2017-11-19 03:46:24 +00:00
if len ( strings . Join ( hostSearch , " " ) ) > validation . MaxDNSSearchListChars {
log := fmt . Sprintf ( "Resolv.conf file '%s' contains search line which length is more than allowed %d chars!" , c . ResolverConfig , validation . MaxDNSSearchListChars )
2017-11-14 02:26:06 +00:00
c . recorder . Event ( c . nodeRef , v1 . EventTypeWarning , "CheckLimitsForResolvConf" , log )
2018-11-09 18:49:10 +00:00
klog . V ( 4 ) . Infof ( "CheckLimitsForResolvConf: " + log )
2017-11-14 02:26:06 +00:00
return
}
return
}
2019-02-12 06:00:53 +00:00
// parseResolvConf reads a resolv.conf file from the given reader, and parses
2017-11-14 02:26:06 +00:00
// it into nameservers, searches and options, possibly returning an error.
func parseResolvConf ( reader io . Reader ) ( nameservers [ ] string , searches [ ] string , options [ ] string , err error ) {
file , err := ioutil . ReadAll ( reader )
if err != nil {
return nil , nil , nil , err
}
// Lines of the form "nameserver 1.2.3.4" accumulate.
nameservers = [ ] string { }
// Lines of the form "search example.com" overrule - last one wins.
searches = [ ] string { }
// Lines of the form "option ndots:5 attempts:2" overrule - last one wins.
// Each option is recorded as an element in the array.
options = [ ] string { }
2018-08-31 06:29:58 +00:00
var allErrors [ ] error
2017-11-14 02:26:06 +00:00
lines := strings . Split ( string ( file ) , "\n" )
for l := range lines {
trimmed := strings . TrimSpace ( lines [ l ] )
if strings . HasPrefix ( trimmed , "#" ) {
continue
}
fields := strings . Fields ( trimmed )
if len ( fields ) == 0 {
continue
}
2018-08-31 06:29:58 +00:00
if fields [ 0 ] == "nameserver" {
if len ( fields ) >= 2 {
nameservers = append ( nameservers , fields [ 1 ] )
} else {
allErrors = append ( allErrors , fmt . Errorf ( "nameserver list is empty " ) )
}
2017-11-14 02:26:06 +00:00
}
if fields [ 0 ] == "search" {
searches = fields [ 1 : ]
}
if fields [ 0 ] == "options" {
options = fields [ 1 : ]
}
}
2018-08-31 06:29:58 +00:00
return nameservers , searches , options , utilerrors . NewAggregate ( allErrors )
2017-11-14 02:26:06 +00:00
}
2018-09-10 09:32:28 +00:00
func ( c * Configurer ) getHostDNSConfig ( ) ( * runtimeapi . DNSConfig , error ) {
2017-11-14 02:26:06 +00:00
var hostDNS , hostSearch , hostOptions [ ] string
// Get host DNS settings
if c . ResolverConfig != "" {
f , err := os . Open ( c . ResolverConfig )
if err != nil {
2017-11-17 02:44:13 +00:00
return nil , err
2017-11-14 02:26:06 +00:00
}
defer f . Close ( )
hostDNS , hostSearch , hostOptions , err = parseResolvConf ( f )
if err != nil {
2017-11-17 02:44:13 +00:00
return nil , err
2017-11-14 02:26:06 +00:00
}
}
2017-11-19 03:46:24 +00:00
return & runtimeapi . DNSConfig {
Servers : hostDNS ,
Searches : hostSearch ,
Options : hostOptions ,
} , nil
}
func getPodDNSType ( pod * v1 . Pod ) ( podDNSType , error ) {
dnsPolicy := pod . Spec . DNSPolicy
switch dnsPolicy {
case v1 . DNSNone :
2019-01-11 18:39:54 +00:00
return podDNSNone , nil
2017-11-19 03:46:24 +00:00
case v1 . DNSClusterFirstWithHostNet :
return podDNSCluster , nil
case v1 . DNSClusterFirst :
if ! kubecontainer . IsHostNetworkPod ( pod ) {
return podDNSCluster , nil
}
// Fallback to DNSDefault for pod on hostnetowrk.
fallthrough
case v1 . DNSDefault :
return podDNSHost , nil
}
// This should not happen as kube-apiserver should have rejected
// invalid dnsPolicy.
return podDNSCluster , fmt . Errorf ( fmt . Sprintf ( "invalid DNSPolicy=%v" , dnsPolicy ) )
}
2019-02-12 06:00:53 +00:00
// mergeDNSOptions merges DNS options. If duplicated, entries given by PodDNSConfigOption will
2017-11-19 03:46:24 +00:00
// overwrite the existing ones.
func mergeDNSOptions ( existingDNSConfigOptions [ ] string , dnsConfigOptions [ ] v1 . PodDNSConfigOption ) [ ] string {
optionsMap := make ( map [ string ] string )
for _ , op := range existingDNSConfigOptions {
if index := strings . Index ( op , ":" ) ; index != - 1 {
optionsMap [ op [ : index ] ] = op [ index + 1 : ]
} else {
optionsMap [ op ] = ""
}
}
for _ , op := range dnsConfigOptions {
if op . Value != nil {
optionsMap [ op . Name ] = * op . Value
} else {
optionsMap [ op . Name ] = ""
}
}
// Reconvert DNS options into a string array.
options := [ ] string { }
for opName , opValue := range optionsMap {
op := opName
if opValue != "" {
op = op + ":" + opValue
}
options = append ( options , op )
2017-11-14 02:26:06 +00:00
}
2017-11-19 03:46:24 +00:00
return options
}
// appendDNSConfig appends DNS servers, search paths and options given by
// PodDNSConfig to the existing DNS config. Duplicated entries will be merged.
// This assumes existingDNSConfig and dnsConfig are not nil.
func appendDNSConfig ( existingDNSConfig * runtimeapi . DNSConfig , dnsConfig * v1 . PodDNSConfig ) * runtimeapi . DNSConfig {
existingDNSConfig . Servers = omitDuplicates ( append ( existingDNSConfig . Servers , dnsConfig . Nameservers ... ) )
existingDNSConfig . Searches = omitDuplicates ( append ( existingDNSConfig . Searches , dnsConfig . Searches ... ) )
existingDNSConfig . Options = mergeDNSOptions ( existingDNSConfig . Options , dnsConfig . Options )
return existingDNSConfig
}
2017-11-14 02:26:06 +00:00
2018-02-09 06:53:53 +00:00
// GetPodDNS returns DNS settings for the pod.
2017-11-19 03:46:24 +00:00
func ( c * Configurer ) GetPodDNS ( pod * v1 . Pod ) ( * runtimeapi . DNSConfig , error ) {
2018-09-10 09:32:28 +00:00
dnsConfig , err := c . getHostDNSConfig ( )
2017-11-19 03:46:24 +00:00
if err != nil {
return nil , err
}
dnsType , err := getPodDNSType ( pod )
if err != nil {
2018-11-09 18:49:10 +00:00
klog . Errorf ( "Failed to get DNS type for pod %q: %v. Falling back to DNSClusterFirst policy." , format . Pod ( pod ) , err )
2017-11-19 03:46:24 +00:00
dnsType = podDNSCluster
}
switch dnsType {
case podDNSNone :
// DNSNone should use empty DNS settings as the base.
dnsConfig = & runtimeapi . DNSConfig { }
case podDNSCluster :
if len ( c . clusterDNS ) != 0 {
// For a pod with DNSClusterFirst policy, the cluster DNS server is
// the only nameserver configured for the pod. The cluster DNS server
// itself will forward queries to other nameservers that is configured
// to use, in case the cluster DNS server cannot resolve the DNS query
// itself.
dnsConfig . Servers = [ ] string { }
for _ , ip := range c . clusterDNS {
dnsConfig . Servers = append ( dnsConfig . Servers , ip . String ( ) )
}
dnsConfig . Searches = c . generateSearchesForDNSClusterFirst ( dnsConfig . Searches , pod )
dnsConfig . Options = defaultDNSOptions
break
}
// clusterDNS is not known. Pod with ClusterDNSFirst Policy cannot be created.
nodeErrorMsg := fmt . Sprintf ( "kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to %q policy." , v1 . DNSClusterFirst , v1 . DNSDefault )
c . recorder . Eventf ( c . nodeRef , v1 . EventTypeWarning , "MissingClusterDNS" , nodeErrorMsg )
c . recorder . Eventf ( pod , v1 . EventTypeWarning , "MissingClusterDNS" , "pod: %q. %s" , format . Pod ( pod ) , nodeErrorMsg )
// Fallback to DNSDefault.
fallthrough
case podDNSHost :
2017-11-14 02:26:06 +00:00
// When the kubelet --resolv-conf flag is set to the empty string, use
// DNS settings that override the docker default (which is to use
// /etc/resolv.conf) and effectively disable DNS lookups. According to
// the bind documentation, the behavior of the DNS client library when
// "nameservers" are not specified is to "use the nameserver on the
// local machine". A nameserver setting of localhost is equivalent to
// this documented behavior.
if c . ResolverConfig == "" {
switch {
case c . nodeIP == nil || c . nodeIP . To4 ( ) != nil :
2017-11-19 03:46:24 +00:00
dnsConfig . Servers = [ ] string { "127.0.0.1" }
2017-11-14 02:26:06 +00:00
case c . nodeIP . To16 ( ) != nil :
2017-11-19 03:46:24 +00:00
dnsConfig . Servers = [ ] string { "::1" }
2017-11-14 02:26:06 +00:00
}
2017-11-19 03:46:24 +00:00
dnsConfig . Searches = [ ] string { "." }
2017-11-14 02:26:06 +00:00
}
}
2019-01-11 18:39:54 +00:00
if pod . Spec . DNSConfig != nil {
2017-11-19 03:46:24 +00:00
dnsConfig = appendDNSConfig ( dnsConfig , pod . Spec . DNSConfig )
2017-11-14 02:26:06 +00:00
}
2017-11-19 03:46:24 +00:00
return c . formDNSConfigFitsLimits ( dnsConfig , pod ) , nil
2017-11-14 02:26:06 +00:00
}
// SetupDNSinContainerizedMounter replaces the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
func ( c * Configurer ) SetupDNSinContainerizedMounter ( mounterPath string ) {
resolvePath := filepath . Join ( strings . TrimSuffix ( mounterPath , "/mounter" ) , "rootfs" , "etc" , "resolv.conf" )
dnsString := ""
for _ , dns := range c . clusterDNS {
dnsString = dnsString + fmt . Sprintf ( "nameserver %s\n" , dns )
}
if c . ResolverConfig != "" {
f , err := os . Open ( c . ResolverConfig )
defer f . Close ( )
if err != nil {
2018-11-09 18:49:10 +00:00
klog . Error ( "Could not open resolverConf file" )
2017-11-14 02:26:06 +00:00
} else {
_ , hostSearch , _ , err := parseResolvConf ( f )
if err != nil {
2018-11-09 18:49:10 +00:00
klog . Errorf ( "Error for parsing the reslov.conf file: %v" , err )
2017-11-14 02:26:06 +00:00
} else {
dnsString = dnsString + "search"
for _ , search := range hostSearch {
dnsString = dnsString + fmt . Sprintf ( " %s" , search )
}
dnsString = dnsString + "\n"
}
}
}
if err := ioutil . WriteFile ( resolvePath , [ ] byte ( dnsString ) , 0600 ) ; err != nil {
2018-11-09 18:49:10 +00:00
klog . Errorf ( "Could not write dns nameserver in file %s, with error %v" , resolvePath , err )
2017-11-14 02:26:06 +00:00
}
}