DNS и kubernetes

Если бы я был абсолютным злом, я бы сломал DNS по всему миру, взял попкорн и стал наблюдать за тем, как вы мучитесь.

Общие вопросы

Все примеры и допущения будут приводиться с учётом того, что кластер Kubernetes поставлен с использованием kubespray.
Для проверки работы DNS будем использовать образ infoblox/dnstools:

kubectl run -it --rm --restart=Never --image=infoblox/dnstools:latest dnstools

Домен для кластера

Домен кластера определяется при установке кластера. Посмотрите в kubespray inventory вашего кластера. Нас интересует файл group_vars/k8s-cluster/k8s-cluster.yml

# DNS configuration.
# Kubernetes cluster name, also will be used as DNS domain
cluster_name: cluster.local

При установке кластера мы не меняли значения этого поля.

Записи A и AAAA

Для services

В системе можно выделить два типа сервисов: обычные и headless.

Обычным сервисам присваивается ip адрес, headless нет.

При обращении к обычному сервису, к его IP адресу, происходит NAT преобразование «подставляющее» IP адрес пода (подов).

headless service по своей сути – это запись типа А в DNS, указывающая непосредственно на IP пода.

Типичное FQDN имя сервиса:

service-name.namespace-name.svc.cluster.local

Для pods

FQDN имя для простого пода:

pod-ip-address.namespace-name.pod.cluster.local

Например:

10-233-79-186.default.pod.cluster.local

DNS сервер кластера

Схема kubespray

Kubespray ставит следующие компоненты системы:

  1. coredns – основной DNS сервер, отвечающий за разрешение имен внутри кластера Kubernetes.
  2. nodelocaldns – кеширующий DNS сервер. По одному на каждую ноду кластера.
  3. dns-autoscaler – приложение, автоматически увеличивающее или уменьшающее количество подов coredns в кластере (https://github.com/kubernetes-sigs/cluster-proportional-vertical-autoscaler).

Основным DNS сервером является coredns (https://coredns.io). На него ложится все преобразования внутри кластера DNS.

Для доступа и распределения запросов между подами coredns, создан соответствующий сервис. На самом деле сервис один, хотя на схеме показаны три штуки. Но мы знаем, что сервис – это набор правил NAT преобразований на каждой ноде кластера.

Nodelocaldns выполнен в виде daemonSet. Это значит, что по одному экземпляру пода будет запущено на каждом кластере сети. Задача nodelocaldns – кеширование запросов от приложений, расположенных на ноде.

Файлы манифестов всех системных компонент кластера, установленного при помощи kubespray, можно найти в директории /etc/kubernetes. В том числе configMaps системы DNS: coredns-config.yml, nodelocaldns-config.yml.

Рассмотрим конфигурацию coredns.

.:53 {
  errors
    health {
    lameduck 5s
  }
  ready
  kubernetes cluster.local in-addr.arpa ip6.arpa {
    pods insecure
    fallthrough in-addr.arpa ip6.arpa
  }
  prometheus :9153
  forward . /etc/resolv.conf {
    prefer_udp
  }
  cache 30
  loop
  reload
  loadbalance
}

Как видно из файла, сервер отвечает на все запросы на 53 порту. Модуль Kubernetes отвечает за домен cluster.local и обратные преобразования. Все остальные запросы будут пересылаться на DNS сервера, описанные в файле /etc/resolv.conf того компьютера, на котором запущен этот под.

Модуль кubernetes реализует концепцию Kubernetes DNS-Based Service Discovery (https://github.com/kubernetes/dns/blob/master/docs/specification.md).

Кеширующий DNS сервер тоже реализован с использованием coredns, но с другой конфигурацией.

cluster.local:53 {
  errors
  cache {
    success 9984 30
    denial 9984 5
  }
  reload
  loop
  bind 169.254.25.10
  forward . 10.233.0.3 {
    force_tcp
  }
  prometheus :9253
  health 169.254.25.10:9254
}
in-addr.arpa:53 {
  errors
  cache 30
  reload
  loop
  bind 169.254.25.10
  forward . 10.233.0.3 {
    force_tcp
  }
  prometheus :9253
}
ip6.arpa:53 {
  errors
  cache 30
  reload
  loop
  bind 169.254.25.10
  forward . 10.233.0.3 {
    force_tcp
  }
  prometheus :9253
}
.:53 {
  errors
  cache 30
  reload
  loop
  bind 169.254.25.10
  forward . /etc/resolv.conf
  prometheus :9253
}

Обратите внимание на схеме приложения обращаются напрямую на 53-й порт DNS сервера. Service для доступа к подам не определен.

Основная задача кеширущего сервера – получать запросы от приложений, расположенных непосредственно на ноде. Если определить сервис, то мы не сможем ограничить запросы только на данный сервер. Поэтому разработчики пошли на хитрость:

bind 169.254.25.10

Сеть 169.254.0.0/16 – это так называемая Link-Local (см. RFC 3927). Подсети link-local не маршрутизируются: маршрутизаторы не должны отправлять пакеты с адресами link-local в другие сети. Система поднимает IP 169.254.25.10 на каждой ноде. С другой стороны, DaemonSet запускает на каждой ноде под nodelocaldns, в котором DNS сервер будет открывать 53-й порт на этом IP.

В результате, на каждой ноде мы получаем кеширующий DNS сервер, находящийся на одном и том же IP. Теперь достаточно сконфигурировать resolver в каждом поде на использование DNS на IP 169.254.25.10.

IP адрес задаётся в конфигурационном файле kubespray inventory: group_vars/k8s-cluster/k8s-cluster.yml

# Enable nodelocal dns cache
enable_nodelocaldns: true
nodelocaldns_ip: 169.254.25.10
nodelocaldns_health_port: 9254

Запросы к внутренним зонам кластера (cluster.local, in-addr.arpa и ip6.arpa), будут пересылаться на service coredns. Но поскольку мы настраиваем пересылку в DNS сервере, мы должны использовать IP адрес сервиса. В kubespray по умолчанию это 10.233.0.3.

Еще один интересный момент:

force_tcp

Кеширующие DNS сервера ходят к coredns по протоколу TCP. Разработчики говорят, что в связи с большим количеством NAT преобразований внутри сети Kubernetes, использование TCP ускоряет доставку запросов.

Запросы на все остальные домены пересылаются на DNS сервера, определённые в стандартных файлах /etc/resolv.conf, находящихся на нодах, где запущены поды nodelocaldns. Важно отметить, что данные из этих файлов читаются только при старте пода. Поэтому внесение изменений в resolv.conf не повлияет на работу кеширующего DNS сервера. Поэтому, после изменения этого файла, перезапустите nodelocaldns на этой ноде.

Настройки pod

Hostname и subdomain

По умолчанию под имеет только краткое имя. Если по каким-то причинам ему надо задать FQDN имя, его можно определить непосредственно в yaml файле.

apiVersion: v1
kind: Pod
metadata:
  name: dnstools
  namespace: default
spec:
  hostname: dns
  subdomain: tools
  containers:
    - name: dnstools
      image: infoblox/dnstools:latest
      command:
        - sleep
        - "36000"
      imagePullPolicy: IfNotPresent
  restartPolicy: Always

В итоге у контейнера будет установлено имя:

dns.tools.default.svc.cluster.local

Преобразование имени в ip адрес будет добавлено в файл /etc/hosts контейнера.

dnstools# cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
fe00::0 ip6-mcastprefix
fe00::1 ip6-allnodes
fe00::2 ip6-allrouters
10.233.79.189 dns.tools.default.svc.cluster.local dns

В зону DNS это преобразование добавляться не будет!

Файл hosts

Для добавления записей в файл /etc/hosts контейнеров пода можно использовать следующую конструкцию:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dnstools
spec:
  replicas: 1
  selector:
    matchLabels:
      app: dnstools
  template:
    metadata:
      labels:
        app: dnstools
    spec:
      hostname: dns
      subdomain: tools
      hostAliases:
        - ip: 8.8.8.8
          hostnames:
            - dns.google.local
            - dns8.google.local
        - ip: 8.8.4.4
          hostnames:
            - dns4.google.local
      containers:
        - name: dnstools
          image: infoblox/dnstools:latest
          command:
            - sleep
            - "36000"
          imagePullPolicy: IfNotPresent
      restartPolicy: Always

Смотрите секцию hostAliases.

Pod – конфигурация resolver

При описании пода можно вместо стандартного файла /etc/resolvr.conf настроить свой собственный вариант.

В этом случае необходимо изменить dnsPolicy на none:

dnsPolicy: "None"

Эта политика заставляет под игнорировать настройки DNS Kubernetes. При этом требуется настроить DNS с использованием параметра

dnsConfig:
  nameservers:
    - 8.8.8.8
    - 8.8.4.4
  searches:
     - kryukov.biz
   options:
     - name: ndots
       value: "2"