dns.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package dns
  14. import (
  15. "fmt"
  16. "io"
  17. "io/ioutil"
  18. "net"
  19. "os"
  20. "path/filepath"
  21. "strings"
  22. "k8s.io/api/core/v1"
  23. utilerrors "k8s.io/apimachinery/pkg/util/errors"
  24. "k8s.io/client-go/tools/record"
  25. runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
  26. "k8s.io/kubernetes/pkg/apis/core/validation"
  27. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  28. "k8s.io/kubernetes/pkg/kubelet/util/format"
  29. "k8s.io/klog"
  30. )
  31. var (
  32. // The default dns opt strings.
  33. defaultDNSOptions = []string{"ndots:5"}
  34. )
  35. type podDNSType int
  36. const (
  37. podDNSCluster podDNSType = iota
  38. podDNSHost
  39. podDNSNone
  40. )
  41. // Configurer is used for setting up DNS resolver configuration when launching pods.
  42. type Configurer struct {
  43. recorder record.EventRecorder
  44. nodeRef *v1.ObjectReference
  45. nodeIP net.IP
  46. // If non-nil, use this for container DNS server.
  47. clusterDNS []net.IP
  48. // If non-empty, use this for container DNS search.
  49. ClusterDomain string
  50. // The path to the DNS resolver configuration file used as the base to generate
  51. // the container's DNS resolver configuration file. This can be used in
  52. // conjunction with clusterDomain and clusterDNS.
  53. ResolverConfig string
  54. }
  55. // NewConfigurer returns a DNS configurer for launching pods.
  56. func NewConfigurer(recorder record.EventRecorder, nodeRef *v1.ObjectReference, nodeIP net.IP, clusterDNS []net.IP, clusterDomain, resolverConfig string) *Configurer {
  57. return &Configurer{
  58. recorder: recorder,
  59. nodeRef: nodeRef,
  60. nodeIP: nodeIP,
  61. clusterDNS: clusterDNS,
  62. ClusterDomain: clusterDomain,
  63. ResolverConfig: resolverConfig,
  64. }
  65. }
  66. func omitDuplicates(strs []string) []string {
  67. uniqueStrs := make(map[string]bool)
  68. var ret []string
  69. for _, str := range strs {
  70. if !uniqueStrs[str] {
  71. ret = append(ret, str)
  72. uniqueStrs[str] = true
  73. }
  74. }
  75. return ret
  76. }
  77. func (c *Configurer) formDNSSearchFitsLimits(composedSearch []string, pod *v1.Pod) []string {
  78. limitsExceeded := false
  79. if len(composedSearch) > validation.MaxDNSSearchPaths {
  80. composedSearch = composedSearch[:validation.MaxDNSSearchPaths]
  81. limitsExceeded = true
  82. }
  83. if resolvSearchLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchLineStrLen > validation.MaxDNSSearchListChars {
  84. cutDomainsNum := 0
  85. cutDomainsLen := 0
  86. for i := len(composedSearch) - 1; i >= 0; i-- {
  87. cutDomainsLen += len(composedSearch[i]) + 1
  88. cutDomainsNum++
  89. if (resolvSearchLineStrLen - cutDomainsLen) <= validation.MaxDNSSearchListChars {
  90. break
  91. }
  92. }
  93. composedSearch = composedSearch[:(len(composedSearch) - cutDomainsNum)]
  94. limitsExceeded = true
  95. }
  96. if limitsExceeded {
  97. log := fmt.Sprintf("Search Line limits were exceeded, some search paths have been omitted, the applied search line is: %s", strings.Join(composedSearch, " "))
  98. c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", log)
  99. klog.Error(log)
  100. }
  101. return composedSearch
  102. }
  103. func (c *Configurer) formDNSNameserversFitsLimits(nameservers []string, pod *v1.Pod) []string {
  104. if len(nameservers) > validation.MaxDNSNameservers {
  105. nameservers = nameservers[0:validation.MaxDNSNameservers]
  106. log := fmt.Sprintf("Nameserver limits were exceeded, some nameservers have been omitted, the applied nameserver line is: %s", strings.Join(nameservers, " "))
  107. c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", log)
  108. klog.Error(log)
  109. }
  110. return nameservers
  111. }
  112. func (c *Configurer) formDNSConfigFitsLimits(dnsConfig *runtimeapi.DNSConfig, pod *v1.Pod) *runtimeapi.DNSConfig {
  113. dnsConfig.Servers = c.formDNSNameserversFitsLimits(dnsConfig.Servers, pod)
  114. dnsConfig.Searches = c.formDNSSearchFitsLimits(dnsConfig.Searches, pod)
  115. return dnsConfig
  116. }
  117. func (c *Configurer) generateSearchesForDNSClusterFirst(hostSearch []string, pod *v1.Pod) []string {
  118. if c.ClusterDomain == "" {
  119. return hostSearch
  120. }
  121. nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, c.ClusterDomain)
  122. svcDomain := fmt.Sprintf("svc.%s", c.ClusterDomain)
  123. clusterSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain}
  124. return omitDuplicates(append(clusterSearch, hostSearch...))
  125. }
  126. // CheckLimitsForResolvConf checks limits in resolv.conf.
  127. func (c *Configurer) CheckLimitsForResolvConf() {
  128. f, err := os.Open(c.ResolverConfig)
  129. if err != nil {
  130. c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error())
  131. klog.V(4).Infof("CheckLimitsForResolvConf: " + err.Error())
  132. return
  133. }
  134. defer f.Close()
  135. _, hostSearch, _, err := parseResolvConf(f)
  136. if err != nil {
  137. c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error())
  138. klog.V(4).Infof("CheckLimitsForResolvConf: " + err.Error())
  139. return
  140. }
  141. domainCountLimit := validation.MaxDNSSearchPaths
  142. if c.ClusterDomain != "" {
  143. domainCountLimit -= 3
  144. }
  145. if len(hostSearch) > domainCountLimit {
  146. log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", c.ResolverConfig, domainCountLimit)
  147. c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
  148. klog.V(4).Infof("CheckLimitsForResolvConf: " + log)
  149. return
  150. }
  151. if len(strings.Join(hostSearch, " ")) > validation.MaxDNSSearchListChars {
  152. log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", c.ResolverConfig, validation.MaxDNSSearchListChars)
  153. c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
  154. klog.V(4).Infof("CheckLimitsForResolvConf: " + log)
  155. return
  156. }
  157. return
  158. }
  159. // parseResolvConf reads a resolv.conf file from the given reader, and parses
  160. // it into nameservers, searches and options, possibly returning an error.
  161. func parseResolvConf(reader io.Reader) (nameservers []string, searches []string, options []string, err error) {
  162. file, err := ioutil.ReadAll(reader)
  163. if err != nil {
  164. return nil, nil, nil, err
  165. }
  166. // Lines of the form "nameserver 1.2.3.4" accumulate.
  167. nameservers = []string{}
  168. // Lines of the form "search example.com" overrule - last one wins.
  169. searches = []string{}
  170. // Lines of the form "option ndots:5 attempts:2" overrule - last one wins.
  171. // Each option is recorded as an element in the array.
  172. options = []string{}
  173. var allErrors []error
  174. lines := strings.Split(string(file), "\n")
  175. for l := range lines {
  176. trimmed := strings.TrimSpace(lines[l])
  177. if strings.HasPrefix(trimmed, "#") {
  178. continue
  179. }
  180. fields := strings.Fields(trimmed)
  181. if len(fields) == 0 {
  182. continue
  183. }
  184. if fields[0] == "nameserver" {
  185. if len(fields) >= 2 {
  186. nameservers = append(nameservers, fields[1])
  187. } else {
  188. allErrors = append(allErrors, fmt.Errorf("nameserver list is empty "))
  189. }
  190. }
  191. if fields[0] == "search" {
  192. searches = fields[1:]
  193. }
  194. if fields[0] == "options" {
  195. options = fields[1:]
  196. }
  197. }
  198. return nameservers, searches, options, utilerrors.NewAggregate(allErrors)
  199. }
  200. func (c *Configurer) getHostDNSConfig() (*runtimeapi.DNSConfig, error) {
  201. var hostDNS, hostSearch, hostOptions []string
  202. // Get host DNS settings
  203. if c.ResolverConfig != "" {
  204. f, err := os.Open(c.ResolverConfig)
  205. if err != nil {
  206. return nil, err
  207. }
  208. defer f.Close()
  209. hostDNS, hostSearch, hostOptions, err = parseResolvConf(f)
  210. if err != nil {
  211. return nil, err
  212. }
  213. }
  214. return &runtimeapi.DNSConfig{
  215. Servers: hostDNS,
  216. Searches: hostSearch,
  217. Options: hostOptions,
  218. }, nil
  219. }
  220. func getPodDNSType(pod *v1.Pod) (podDNSType, error) {
  221. dnsPolicy := pod.Spec.DNSPolicy
  222. switch dnsPolicy {
  223. case v1.DNSNone:
  224. return podDNSNone, nil
  225. case v1.DNSClusterFirstWithHostNet:
  226. return podDNSCluster, nil
  227. case v1.DNSClusterFirst:
  228. if !kubecontainer.IsHostNetworkPod(pod) {
  229. return podDNSCluster, nil
  230. }
  231. // Fallback to DNSDefault for pod on hostnetowrk.
  232. fallthrough
  233. case v1.DNSDefault:
  234. return podDNSHost, nil
  235. }
  236. // This should not happen as kube-apiserver should have rejected
  237. // invalid dnsPolicy.
  238. return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v", dnsPolicy))
  239. }
  240. // mergeDNSOptions merges DNS options. If duplicated, entries given by PodDNSConfigOption will
  241. // overwrite the existing ones.
  242. func mergeDNSOptions(existingDNSConfigOptions []string, dnsConfigOptions []v1.PodDNSConfigOption) []string {
  243. optionsMap := make(map[string]string)
  244. for _, op := range existingDNSConfigOptions {
  245. if index := strings.Index(op, ":"); index != -1 {
  246. optionsMap[op[:index]] = op[index+1:]
  247. } else {
  248. optionsMap[op] = ""
  249. }
  250. }
  251. for _, op := range dnsConfigOptions {
  252. if op.Value != nil {
  253. optionsMap[op.Name] = *op.Value
  254. } else {
  255. optionsMap[op.Name] = ""
  256. }
  257. }
  258. // Reconvert DNS options into a string array.
  259. options := []string{}
  260. for opName, opValue := range optionsMap {
  261. op := opName
  262. if opValue != "" {
  263. op = op + ":" + opValue
  264. }
  265. options = append(options, op)
  266. }
  267. return options
  268. }
  269. // appendDNSConfig appends DNS servers, search paths and options given by
  270. // PodDNSConfig to the existing DNS config. Duplicated entries will be merged.
  271. // This assumes existingDNSConfig and dnsConfig are not nil.
  272. func appendDNSConfig(existingDNSConfig *runtimeapi.DNSConfig, dnsConfig *v1.PodDNSConfig) *runtimeapi.DNSConfig {
  273. existingDNSConfig.Servers = omitDuplicates(append(existingDNSConfig.Servers, dnsConfig.Nameservers...))
  274. existingDNSConfig.Searches = omitDuplicates(append(existingDNSConfig.Searches, dnsConfig.Searches...))
  275. existingDNSConfig.Options = mergeDNSOptions(existingDNSConfig.Options, dnsConfig.Options)
  276. return existingDNSConfig
  277. }
  278. // GetPodDNS returns DNS settings for the pod.
  279. func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
  280. dnsConfig, err := c.getHostDNSConfig()
  281. if err != nil {
  282. return nil, err
  283. }
  284. dnsType, err := getPodDNSType(pod)
  285. if err != nil {
  286. klog.Errorf("Failed to get DNS type for pod %q: %v. Falling back to DNSClusterFirst policy.", format.Pod(pod), err)
  287. dnsType = podDNSCluster
  288. }
  289. switch dnsType {
  290. case podDNSNone:
  291. // DNSNone should use empty DNS settings as the base.
  292. dnsConfig = &runtimeapi.DNSConfig{}
  293. case podDNSCluster:
  294. if len(c.clusterDNS) != 0 {
  295. // For a pod with DNSClusterFirst policy, the cluster DNS server is
  296. // the only nameserver configured for the pod. The cluster DNS server
  297. // itself will forward queries to other nameservers that is configured
  298. // to use, in case the cluster DNS server cannot resolve the DNS query
  299. // itself.
  300. dnsConfig.Servers = []string{}
  301. for _, ip := range c.clusterDNS {
  302. dnsConfig.Servers = append(dnsConfig.Servers, ip.String())
  303. }
  304. dnsConfig.Searches = c.generateSearchesForDNSClusterFirst(dnsConfig.Searches, pod)
  305. dnsConfig.Options = defaultDNSOptions
  306. break
  307. }
  308. // clusterDNS is not known. Pod with ClusterDNSFirst Policy cannot be created.
  309. 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)
  310. c.recorder.Eventf(c.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", nodeErrorMsg)
  311. c.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "pod: %q. %s", format.Pod(pod), nodeErrorMsg)
  312. // Fallback to DNSDefault.
  313. fallthrough
  314. case podDNSHost:
  315. // When the kubelet --resolv-conf flag is set to the empty string, use
  316. // DNS settings that override the docker default (which is to use
  317. // /etc/resolv.conf) and effectively disable DNS lookups. According to
  318. // the bind documentation, the behavior of the DNS client library when
  319. // "nameservers" are not specified is to "use the nameserver on the
  320. // local machine". A nameserver setting of localhost is equivalent to
  321. // this documented behavior.
  322. if c.ResolverConfig == "" {
  323. switch {
  324. case c.nodeIP == nil || c.nodeIP.To4() != nil:
  325. dnsConfig.Servers = []string{"127.0.0.1"}
  326. case c.nodeIP.To16() != nil:
  327. dnsConfig.Servers = []string{"::1"}
  328. }
  329. dnsConfig.Searches = []string{"."}
  330. }
  331. }
  332. if pod.Spec.DNSConfig != nil {
  333. dnsConfig = appendDNSConfig(dnsConfig, pod.Spec.DNSConfig)
  334. }
  335. return c.formDNSConfigFitsLimits(dnsConfig, pod), nil
  336. }
  337. // SetupDNSinContainerizedMounter replaces the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
  338. func (c *Configurer) SetupDNSinContainerizedMounter(mounterPath string) {
  339. resolvePath := filepath.Join(strings.TrimSuffix(mounterPath, "/mounter"), "rootfs", "etc", "resolv.conf")
  340. dnsString := ""
  341. for _, dns := range c.clusterDNS {
  342. dnsString = dnsString + fmt.Sprintf("nameserver %s\n", dns)
  343. }
  344. if c.ResolverConfig != "" {
  345. f, err := os.Open(c.ResolverConfig)
  346. defer f.Close()
  347. if err != nil {
  348. klog.Error("Could not open resolverConf file")
  349. } else {
  350. _, hostSearch, _, err := parseResolvConf(f)
  351. if err != nil {
  352. klog.Errorf("Error for parsing the reslov.conf file: %v", err)
  353. } else {
  354. dnsString = dnsString + "search"
  355. for _, search := range hostSearch {
  356. dnsString = dnsString + fmt.Sprintf(" %s", search)
  357. }
  358. dnsString = dnsString + "\n"
  359. }
  360. }
  361. }
  362. if err := ioutil.WriteFile(resolvePath, []byte(dnsString), 0600); err != nil {
  363. klog.Errorf("Could not write dns nameserver in file %s, with error %v", resolvePath, err)
  364. }
  365. }