GrabDuck

CoreDNS — DNS-сервер для мира cloud native и Service Discovery для Kubernetes

:

Две недели назад Open Source-проект CoreDNS отметился своим очередным релизом — 008. Авторы называют свой продукт «DNS-сервером, состоящим из цепочки промежуточных компонентов (middleware), каждый из которых реализует какую-то возможность DNS». Что примечательно, они уже успели добиться включения CoreDNS в список официальных проектов организации CNCF (Cloud Native Computing Foundation), пополнив ряды Kubernetes, Prometheus, CNI, containerd, rkt и других разработок, активно применяемых в мире контейнеров, микросервисов и «родных облачных приложений» ( cloud native).

Как появился CoreDNS, для чего он предназначен и как его можно использовать?

Знакомимся с CoreDNS


CoreDNS — DNS-сервер, появившийся в начале 2016 года (под свободной лицензией Apache License v2) как форк быстрого веб-сервера Caddy, написанного на языке Go. Сам HTTP-сервер в Caddy реализован пакетом httpserver, двумя важнейшими типами в котором являются Handler (функция обработки HTTP-запроса) и Middleware (промежуточный слой, прицепляющий один Handler к другому, — таким образом формируется цепочка из обработчиков). Этот подход с цепочкой из функций переняли и в CoreDNS, что позволило разработчикам описывать своё решение очень лаконично: «CoreDNS — DNS-сервер, сцепляющий middleware».

Второй основополагающей деталью в проекте CoreDNS является его преемственность от SkyDNS — service discovery, построенного поверх NoSQL-хранилища etcd и использующего DNS-запросы (специально сформированные SRV-записи) для обнаружения доступных сервисов. Фактически SkyDNS был лёгкой прослойкой между etcd, где на самом деле хранилась информация о сервисах, и DNS-сервером, через который эта информация становилась «публично» доступной. Судя по всему, активная разработка SkyDNS прекратилась около 7 месяцев назад (с добавлением поддержки etcd 3) в то время, как коммиты в кодовой базе CoreDNS можно наблюдать практически ежедневно.

В CoreDNS не стали ограничиваться etcd как единственным бэкендом для данных, отображаемых в DNS-записях. На данный момент поддерживается ещё Kubernetes (который, впрочем, как известно, тоже использует etcd… ), что позволяет авторам официально позиционировать CoreDNS в качестве замены kube-dns. (Подробнее о том, как эту замену попробовать в действии, читайте ниже.)

Примечание: Полная история взаимоотношений упомянутых DNS-решений: SkyDNS, CoreDNS, kube-dns — переплетена ещё теснее, чем могло показаться. Дело в том, что, во-первых, kube-dns использует библиотеки SkyDNS для обслуживания DNS-запросов, поступающих в поды и сервисы Kubernetes. А во-вторых, главным разработчиком CoreDNS является оригинальный автор SkyDNS — Miek Gieben — SRE из Google, которого также знают в сообществе Go благодаря его DNS-библиотеке. Всё это позволяет увидеть в CoreDNS не «конкурента» kube-dns, а скорее его эволюцию.

Возможности CoreDNS


Этот сервер позволяет принимать запросы по UDP/TCP, TLS (RFC 7858) и gRPC. А функционирует он как:
  • первичный DNS-сервер, отдающий данные о зоне из файла (DNS и DNSSEC) и позволяющий выполнять трансфер зоны;
  • вторичный DNS-сервер (поддерживаются только AXFR);
  • прокси (перенаправляет запросы другим серверам).

Среди других значимых возможностей CoreDNS:
  • кэширование результатов запросов;
  • rewrite для запросов (qtype, qclass, qname);
  • балансировка запросов;
  • проверка состояния (health checking) конечных узлов;
  • метрики в Prometheus;
  • журналирование запросов и ошибок;
  • профилирование.

Все перечисленные функции (включая даже режимы первичного и вторичного сервера) реализуются различными модулями, а точнее (в терминологии Caddy/CoreDNS) — middleware. Очевидно, что возможности легко расширять созданием новых middleware.


Архитектура CoreDNS, где за каждую функцию отвечает свой middleware

John Belamaric, архитектор в Infoblox и один из ведущих разработчиков CoreDNS, считает именно эту особенность главной в проекте:

Существует множество различных DNS-серверов, существуют даже другие решения для обнаружения сервисов, основанные на DNS. Но одним из главных достоинств CoreDNS является то, насколько это решение расширяемое и гибкое. Это позволяет легко адаптировать его под динамичный, часто изменяющийся мир cloud-native.

Почему CoreDNS взяли под опеку в CNCF?


Вот как объясняется это событие на сайте проекта:
Наша цель — сделать CoreDNS DNS-сервером и решением service discovery для cloud-native. CNCF как организация сосредоточена на совершенствовании архитектур для cloud-native. Таким образом, для нас это прекрасное совпадение [преследуемых целей]. Обнаружение сервисов — ключевой компонент в родном облачном пространстве CNCF, и CoreDNS первенствует в этой роли.

В заявлении от имени самой CNCF выступил Chris Aniszczyk (COO в CNCF), отметивший, что «CoreDNS предоставляет важные сервисы для именования и эффективно интегрируется со многими другими проектами проектами категории cloud-native в CNCF», а также «CoreDNS — привлекательный вариант для обнаружения сервисов в Kubernetes».

Примечание: На данный момент CoreDNS имеет начальный статус (inception) среди проектов CNCF, что означает необходимость пересмотра техническим комитетом этого статуса через год и принятия решения о его дальнейшей судьбе: убрать, продлить, повысить до incubating или graduated.

Перейдём от теории к практике.

CoreDNS как Service Discovery для Kubernetes


Как уже писалось в примечании выше, разработчики CoreDNS не просто предлагают альтернативу для kube-dns — они взаимодействуют с сообществом Kubernetes, чтобы результат их работы был полезным для всех. Хороший тому пример — их инициатива по созданию спецификации, описывающей Service Discovery на базе DNS для Kubernetes. Она появилась с тем, чтобы «гарантировать совместимость существующей реализации в Kube-DNS и новой в CoreDNS». Версия 1.0.0 этой спецификации в основном копирует поведение kube-dns — ей соответствуют релизы CoreDNS 005 и выше (предлагая, помимо этого, дополнительные возможности, если сравнивать с kube-dns).

Чтобы начать использовать CoreDNS в качестве Service Discovery в Kubernetes, разработчики подготовили конфигурацию ( ConfigMap и Deployment) и даже Bash-скрипт deploy.sh для быстрого деплоя. Они позаботились и о примере, как этим пользоваться (все дальнейшие листинги взяты из него):

$ ./deploy.sh 10.3.0.0/24 cluster.local

  • 10.3.0.0/24 — CIDRs сервисов;
  • cluster.local (указывается опционально) — доменное имя кластера.

Результатом выполнения скрипта станет такой манифест:
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors
        log stdout
        health
        kubernetes cluster.local {
          cidrs 10.3.0.0/24
        }
        proxy . /etc/resolv.conf
        cache 30
    }
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: coredns
  namespace: kube-system
  labels:
    k8s-app: coredns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "CoreDNS"
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: coredns
  template:
    metadata:
      labels:
        k8s-app: coredns
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ''
        scheduler.alpha.kubernetes.io/tolerations: '[{"key":"CriticalAddonsOnly", "operator":"Exists"}]'
    spec:
      containers:
      - name: coredns
        image: coredns/coredns:latest
        imagePullPolicy: Always
        args: [ "-conf", "/etc/coredns/Corefile" ]
        volumeMounts:
        - name: config-volume
          mountPath: /etc/coredns
        ports:
        - containerPort: 53
          name: dns
          protocol: UDP
        - containerPort: 53
          name: dns-tcp
          protocol: TCP
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          timeoutSeconds: 5
          successThreshold: 1
          failureThreshold: 5
      dnsPolicy: Default
      volumes:
        - name: config-volume
          configMap:
            name: coredns
            items:
            - key: Corefile
              path: Corefile
---
apiVersion: v1
kind: Service
metadata:
  name: kube-dns
  namespace: kube-system
  labels:
    k8s-app: coredns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: "CoreDNS"
spec:
  selector:
    k8s-app: coredns
  clusterIP: 10.3.0.10
  ports:
  - name: dns
    port: 53
    protocol: UDP
  - name: dns-tcp
    port: 53
    protocol: TCP

Указанная здесь (в Corefile) директива cidrs 10.3.0.0/24 сообщает в Kubernetes middleware из CoreDNS, что необходимо обслуживать PTR-запросы для обратной зоны 0.3.10.in-addr.arpa.

Остаётся передать результат на исполнение в Kubernetes:

$ ./deploy.sh 10.3.0.0/24 | kubectl apply -f -
configmap "coredns" created
deployment "coredns" created
service "kube-dns" configured

… и убедиться, что новый DNS-сервер действительно заработал:
$ kubectl run -it --rm --restart=Never --image=infoblox/dnstools:latest dnstools
Waiting for pod default/dnstools to be running, status is Pending, pod ready: false
If you don't see a command prompt, try pressing enter.
# host kubernetes
kubernetes.default.svc.cluster.local has address 10.3.0.1
# host kube-dns.kube-system
kube-dns.kube-system.svc.cluster.local has address 10.3.0.10
# host 10.3.0.1
1.0.3.10.in-addr.arpa domain name pointer kubernetes.default.svc.cluster.local.
# host 10.3.0.10
10.0.3.10.in-addr.arpa domain name pointer kube-dns.kube-system.svc.cluster.local.

Как это выглядит на стороне самого CoreDNS? Вот пример для случая кластера из двух реплик CoreDNS, между которыми делается балансировка DNS-запросов:
# найдем поды со службой CoreDNS
$ kubectl get --namespace kube-system pods
NAME                                    READY     STATUS    RESTARTS   AGE
coredns-3558181428-0zhnh                1/1       Running   0          2m
coredns-3558181428-xri9i                1/1       Running   0          2m
heapster-v1.2.0-4088228293-a8gkc        2/2       Running   0          126d
kube-apiserver-10.222.243.77            1/1       Running   2          126d
kube-controller-manager-10.222.243.77   1/1       Running   2          126d
kube-proxy-10.222.243.77                1/1       Running   2          126d
kube-proxy-10.222.243.78                1/1       Running   0          126d
kube-scheduler-10.222.243.77            1/1       Running   2          126d
kubernetes-dashboard-v1.4.1-gi2xr       1/1       Running   0          24d
tiller-deploy-3299276078-e8phb          1/1       Running   0          24d
# посмотрим на логи первого
$ kubectl logs --namespace kube-system coredns-3558181428-0zhnh
2017/02/23 14:48:29 [INFO] Kubernetes middleware configured without a label selector. No label-based filtering will be performed.
.:53
2017/02/23 14:48:29 [INFO] CoreDNS-005
CoreDNS-005
10.2.6.127 - [23/Feb/2017:14:49:44 +0000] "AAAA IN kubernetes.default.svc.cluster.local. udp 54 false 512" NOERROR 107 544.128µs
10.2.6.127 - [23/Feb/2017:14:49:44 +0000] "MX IN kubernetes.default.svc.cluster.local. udp 54 false 512" NOERROR 107 7.576897ms
10.2.6.127 - [23/Feb/2017:14:49:52 +0000] "A IN kube-dns.kube-system.default.svc.cluster.local. udp 64 false 512" NXDOMAIN 117 471.176µs
23/Feb/2017:14:49:52 +0000 [ERROR 0 kube-dns.kube-system.default.svc.cluster.local. A] no items found
10.2.6.127 - [23/Feb/2017:14:50:00 +0000] "PTR IN 10.0.3.10.in-addr.arpa. udp 40 false 512" NOERROR 92 752.956µs
# посмотрим на логи второго
$ kubectl logs --namespace kube-system coredns-3558181428-xri9i
2017/02/23 14:48:29 [INFO] Kubernetes middleware configured without a label selector. No label-based filtering will be performed.
.:53
2017/02/23 14:48:29 [INFO] CoreDNS-005
CoreDNS-005
10.2.6.127 - [23/Feb/2017:14:49:44 +0000] "A IN kubernetes.default.svc.cluster.local. udp 54 false 512" NOERROR 70 1.10732ms
10.2.6.127 - [23/Feb/2017:14:49:52 +0000] "A IN kube-dns.kube-system.svc.cluster.local. udp 56 false 512" NOERROR 72 409.74µs
10.2.6.127 - [23/Feb/2017:14:49:52 +0000] "AAAA IN kube-dns.kube-system.svc.cluster.local. udp 56 false 512" NOERROR 109 210.817µs
10.2.6.127 - [23/Feb/2017:14:49:52 +0000] "MX IN kube-dns.kube-system.svc.cluster.local. udp 56 false 512" NOERROR 109 796.703µs
10.2.6.127 - [23/Feb/2017:14:49:56 +0000] "PTR IN 1.0.3.10.in-addr.arpa. udp 39 false 512" NOERROR 89 694.649µs

Для отключения логирования всех DNS-запросов (большая нагрузка на диск при реальном применени) достаточно убрать строку log stdout из Corefile.

Важно: авторы предупреждают, что в случае использования Google Container Engine (GKE) описываемый пример не сработает из-за дополнительных процессов, не позволяющих заменить стандартный kube-dns. Есть путь решения этой проблемы, однако официально он пока не был ими документирован/анонсирован.

CoreDNS для Minikube


В случае локального запуска Kubernetes с Minikube есть схожая проблема: используемый в нём addon manager периодически проверяет (и поддерживает) состояние конфигураций всех установленных дополнений, одним из которых является kube-dns. Чтобы этот менеджер не мешал работе CoreDNS, есть простое решение.

Оно заключается в том, чтобы изменить список установленных дополнений для minikube:

$ minikube addons list
- dashboard: enabled
- default-storageclass: enabled
- kube-dns: enabled
- heapster: disabled
- ingress: disabled
- registry-creds: disabled
- addon-manager: enabled
$ minikube addons disable kube-dns
kube-dns was successfully disabled
$ minikube addons list
- heapster: disabled
- ingress: disabled
- registry-creds: disabled
- addon-manager: enabled
- dashboard: enabled
- default-storageclass: enabled
- kube-dns: disabled

Эту настройку необходимо выполнить до применения конфигурации CoreDNS в Kubernetes (т.е. до запуска kubectl apply -f из примера выше). А после применения этой конфигурации потребуется ещё удалить ReplicationController из kube-dns, поскольку отключение дополнения не делает этого автоматически:
$ kubectl get -n kube-system pods
NAME                          READY     STATUS    RESTARTS   AGE
coredns-980047985-g2748       1/1       Running   1          36m
kube-addon-manager-minikube   1/1       Running   0          9d
kube-dns-v20-qzvr2            3/3       Running   0          1m
kubernetes-dashboard-ks1jp    1/1       Running   0          9d
$ kubectl delete -n kube-system rc kube-dns-v20
replicationcontroller "kube-dns-v20" deleted

Заключение


CoreDNS — интересный проект, реальные перспективы которому придают его убедительное наследие (опыт со времён SkyDNS), тесная кооперация с профильным сообществом (Kubernetes и Go), признание в CNCF и, конечно, современный подход к реализации.

Читайте также в нашем блоге по близким темам: