ExternalName в Kubernetes: DNS-алиас для внешней БД, API и других кластеров | AdminWiki

ExternalName в Kubernetes: DNS-алиас для внешней БД, API и других кластеров

30 сентября 2026 9 мин. чтения

ExternalName в Kubernetes закрывает узкую, но частую задачу: поды обращаются к внешнему ресурсу по короткому внутреннему имени, а кластерный DNS отдаёт CNAME-запись на внешний домен. Трафик идёт напрямую, без проксирования через kube-proxy.

Внутри кластера появляется запись вида my-service.default.svc.cluster.local, указывающая на внешний хост. Ни виртуального IP, ни подов, ни балансировщика. Один DNS-ответ CNAME, дальше клиент сам разрешает внешнее имя и открывает соединение.

Ниже разбираем манифест, три рабочих сценария (управляемая база данных, внешний API, сервис в другом кластере), ограничения и проверку через nslookup из пода.

Что такое ExternalName в Kubernetes и зачем он нужен

ExternalName это тип Service, у которого нет ClusterIP, нет селектора и нет портов. Единственное действие, которое он выполняет: создаёт в кластерном DNS CNAME-запись, где левая часть это имя Service в зоне кластера, а правая это значение поля spec.externalName.

Запрос от пода идёт так: клиент обращается к my-db.prod.svc.cluster.local, кластерный DNS возвращает CNAME на внешний хост, клиент получает от внешнего DNS A- или AAAA-запись и открывает TCP-соединение напрямую. Пакет не проходит через kube-proxy и не попадает ни в один под кластера.

По документации Kubernetes, DNS-сервер кластера это единственный способ доступа к сервисам типа ExternalName, а сам такой Service отображает сервис на DNS-имя, а не на типичный селектор (Service | Kubernetes).

Практический смысл в абстракции. Вместо длинного хоста вида mydb.cluster-abc123.us-east-1.rds.amazonaws.com в конфигах приложения стоит короткое имя. Смена внешнего адреса (миграция базы, переезд API на другой домен) сводится к правке одного манифеста, код и ConfigMap остаются нетронутыми.

Чем ExternalName отличается от других типов Service

Таблица показывает, что именно создаёт каждый тип Service и как обходится с трафиком.

Тип ServiceЧто создаётсяClusterIPСелектор и портыПроксирование трафика
ExternalNameCNAME-запись в кластерном DNSнетне используютсянет, соединение прямое
ClusterIPвиртуальный IP внутри кластерададада, через kube-proxy
NodePortClusterIP плюс порт на каждой нодедадада
LoadBalancerвнешний балансировщик у провайдерададада
Headless (clusterIP: None)A-записи всех подов сервисанетданет

Ключевое отличие: ClusterIP, NodePort и LoadBalancer маршрутизируют трафик на поды, ExternalName не маршрутизирует ничего. Он работает только на уровне DNS. Headless Service тоже отдаёт ответ без ClusterIP, но указывает на поды кластера, а не на внешний домен.

Критерии выбора между ClusterIP, NodePort и LoadBalancer с готовыми манифестами разобраны в статье про выбор типа Service в Kubernetes.

Как создать ExternalName Service: готовый манифест

Пошаговое создание и применение манифеста

Шаг 1. Создайте файл external-service.yaml. Понадобятся четыре поля: apiVersion, kind, metadata.name и spec с типом и внешним именем.

apiVersion: v1
kind: Service
metadata:
  name: my-external-service
  namespace: default
spec:
  type: ExternalName
  externalName: db.example.com

В поле externalName указывайте доменное имя. IP-адрес здесь не допускается: значение обязано проходить проверку как DNS-имя по RFC 1123, иначе API-сервер отклонит манифест. По документации Kubernetes, определение hostname по RFC 1123 не допускает числовые IP-адреса, а метка должна состоять из строчных буквенно-цифровых символов или дефиса, начинаться и заканчиваться буквенно-цифровым символом (Object Names and IDs | Kubernetes). Текущая реализация Kubernetes дополнительно требует, чтобы метки начинались с буквы, хотя RFC 1123 технически допускает начало с цифры.

Шаг 2. Примените манифест:

kubectl apply -f external-service.yaml

Шаг 3. Проверьте результат:

kubectl get svc my-external-service
NAME                 TYPE           CLUSTER-IP   EXTERNAL-IP      PORT(S)   AGE
my-external-service  ExternalName   <none>       db.example.com   <none>    10s

В колонке CLUSTER-IP стоит none, в EXTERNAL-IP отображается значение externalName, а PORT(S) пуст. Если добавить в манифест ports или selector, endpoints не создаются: по документации Kubernetes, ExternalName Services не должны автоматически генерировать endpoints на основе селекторов. Прямого подтверждения того, что указанные ports и selector не влияют на маршрутизацию, в официальной документации нет, но endpoints для такого Service не формируются, поэтому направлять трафик через них не на что.

После создания в кластерном DNS появляется запись my-external-service.default.svc.cluster.local с типом CNAME и значением db.example.com. Полный набор команд для деплоя с манифестами и разбор типичных ошибок собраны в руководстве про деплой сервисов в Kubernetes.

Практические сценарии использования ExternalName

Три сценария, где DNS-алиас экономит время и снижает связанность конфигов.

Подключение к управляемой базе данных

apiVersion: v1
kind: Service
metadata:
  name: mydb
  namespace: prod
spec:
  type: ExternalName
  externalName: mydb.cluster-abc123.us-east-1.rds.amazonaws.com

В приложении указывайте host: mydb.prod.svc.cluster.local и порт базы, например 5432 для PostgreSQL или 3306 для MySQL. Порт задаёт клиент, Service его не передаёт. При плановой миграции на новый инстанс правьте только externalName, а переменные окружения и DSN остаются прежними.

Подключение к внешнему API

apiVersion: v1
kind: Service
metadata:
  name: stripe-api
  namespace: default
spec:
  type: ExternalName
  externalName: api.stripe.com

Код обращается к stripe-api.default.svc.cluster.local. Смена провайдера платежей или прокси-хоста делается одной строкой в манифесте, без правок в сервисах.

Осторожно с TLS. Сертификат выписан на внешнее имя, а клиент проверяет то имя, к которому подключается. CNAME не подменяет SNI и не меняет проверку цепочки сертификатов. По RFC 6125, если hostname не совпадает с идентификатором в сертификате, клиент обязан либо уведомить пользователя, либо прервать соединение с ошибкой bad certificate (RFC 6125). Без SNI валидация hostname сертификата в принципе невозможна. Прямого подтверждения того, как именно ведёт себя TLS при обращении через ExternalName CNAME, в материалах нет, поэтому проверьте поведение своего HTTP-клиента до выката в прод.

Подключение к сервису в другом кластере

Если сервис в кластере B опубликован через LoadBalancer или Ingress, в кластере A создайте ExternalName с externalName, равным внешнему хосту той публикации.

apiVersion: v1
kind: Service
metadata:
  name: service-b
  namespace: default
spec:
  type: ExternalName
  externalName: service-b.example.com

Поды кластера A обращаются к service-b.default.svc.cluster.local. Условие работоспособности одно: кластерный DNS кластера A должен разрешать внешнее имя, то есть CoreDNS обязан иметь доступ к апстрим-резолверу или к нужной зоне. В закрытом контуре без выхода наружу запись просто не разрешится.

Ограничения и подводные камни ExternalName

Список ограничений короткий, но каждое ломает часть привычных ожиданий от Service.

Отсутствие портов и селекторов

ExternalName не поддерживает ports и selector. Endpoints не создаются, потому что подов, на которые можно направить трафик, нет. Порт указывает клиент в строке подключения:

host: mydb.prod.svc.cluster.local
port: 5432

Если внешний ресурс слушает несколько портов (например, 5432 и 6379 у связки базы и кэша), создайте отдельный ExternalName Service под каждое назначение или возьмите подход из раздела с альтернативами.

Зависимость от DNS и отсутствие балансировки

ExternalName живёт ровно столько, сколько живёт DNS-ответ. CoreDNS недоступен или не может разрешить внешнее имя, и сервис исчезает для подов. Первое, что проверяют при инциденте, состояние подов CoreDNS в kube-system.

Балансировку Kubernetes не выполняет. DNS вернёт CNAME, клиент разрешит его в набор A-записей внешнего DNS и выберет адрес по своим правилам. Если внешний DNS отдаёт один адрес, весь трафик уйдёт на один узел. Распределение нагрузки остаётся задачей внешнего балансировщика.

Трафик не проксируется, поэтому его не видит kube-proxy, не считают метрики Service и не перехватывает service mesh без отдельной настройки. Сетевые политики применяются к соединению с подом на одном или обоих концах и не относятся к сервисам: по умолчанию под не изолирован для egress и все исходящие соединения разрешены, а политика default deny all egress блокирует в том числе DNS-трафик, поэтому для разрешения DNS нужно добавить отдельную NetworkPolicy к DNS-сервису кластера (Network Policies | Kubernetes).

Health checks у ExternalName нет: кластер не знает, жив ли внешний ресурс, и продолжит отдавать CNAME даже при недоступном хосте. Прямого подтверждения этого в официальной документации в материалах нет, но у ExternalName нет endpoints и kubelet-проб, поэтому проверять доступность внешнего хоста кластеру нечем.

Как проверить работу ExternalName через nslookup

Пошаговая проверка из пода

Запустите временный под с busybox и сразу выполните запрос:

kubectl run -it --rm debug --image=busybox:1.36 --restart=Never -- nslookup my-external-service.default.svc.cluster.local

Флаг --rm удаляет под после выхода, --restart=Never не даёт ему перезапускаться. Для busybox доступна утилита nslookup, для полноценного dig нужен образ с пакетом dnsutils из вашего реестра. Если под уже есть, проверьте из него: kubectl exec -it my-pod -- nslookup my-external-service.default.svc.cluster.local, при условии что утилита установлена в контейнере.

Примерный успешный вывод (формат строк зависит от версии busybox):

Server:    10.96.0.10
Address:   10.96.0.10:53

my-external-service.default.svc.cluster.local canonical name = db.example.com
Name:      db.example.com
Address 1: 203.0.113.10

Строка canonical name = db.example.com подтверждает, что CNAME-запись создана и разрешается. Если видите NXDOMAIN или timeout, пройдите по списку:

  • kubectl get svc my-external-service -n default: объект существует и значение externalName указано верно.
  • kubectl get pods -n kube-system -l k8s-app=kube-dns: поды CoreDNS в статусе Running и Ready.
  • kubectl logs -n kube-system -l k8s-app=kube-dns: в логах нет ошибок разбора и таймаутов форвардинга.
  • nslookup db.example.com 8.8.8.8: внешнее имя вообще разрешается, если нет, проблема на стороне внешнего DNS, а не кластера.
  • Для приватных зон проверьте, что CoreDNS форвардит запросы к нужному внутреннему резолверу, иначе имя не разрешится из подов.

Когда ExternalName не подходит: альтернативы

ExternalName не подходит, когда нужны балансировка, управление портами, health checks, проксирование или наблюдаемость на уровне Service. Варианты замены ниже.

Service без селектора с Endpoints

Service без selector плюс отдельный объект Endpoints (или EndpointSlice) с адресами внешних узлов. Порты задаются явно, балансировка и health checks работают как у обычного сервиса.

apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  ports:
    - port: 5432
      targetPort: 5432
---
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db
subsets:
  - addresses:
      - ip: 192.0.2.1
      - ip: 192.0.2.2
    ports:
      - port: 5432

Цена подхода: адреса нужно обновлять вручную при смене инфраструктуры, автоматического разрешения DNS здесь нет. Для нескольких реплик внешней базы это единственный способ получить распределение соединений средствами кластера.

Другие варианты зависят от задачи:

  • Ingress: для внешних HTTP/HTTPS с TLS, маршрутизацией по хостам и путям, плюс балансировка на стороне контроллера. Разбор с манифестами есть в статье про Service и Ingress в Kubernetes.
  • Headless Service: доступ к подам напрямую без ClusterIP, когда клиенту нужны A-записи каждого пода.
  • Service Mesh (Istio, Linkerd): управление трафиком, повторы, mTLS и наблюдаемость, включая случаи обращения к внешним хостам.
  • External IP (externalIPs, MetalLB): обратная задача, опубликовать наружу сервис самого кластера. Методы собраны в статье про настройку External IP в Kubernetes.

Чек-лист перед применением ExternalName: externalName это DNS-имя, а не IP; порт прописан в конфиге приложения; внешнее имя разрешается из подов через CoreDNS; TLS-проверка проходит с тем именем, к которому подключается клиент. Если хотя бы один пункт не выполняется, берите Service без селектора с Endpoints.

Поделиться: