Если в кластере нужно публиковать часть сервисов в интернет, а часть оставлять доступной только из приватной сети, удобнее всего разделить входящий трафик на уровне ingress-контроллеров. Для этого можно развернуть два экземпляра Traefik: один будет обслуживать публичные Ingress, второй — приватные.
В этой статье разберем такой сценарий. В качестве основы будем использовать два Helm-релиза Traefik с разными IngressClass, а затем покажем, как маршрутизировать запросы к публичным и внутренним сервисам.
Идея в том, что в кластере запускаются два независимых экземпляра Traefik:
-
traefik-public— обрабатывает только Ingress сingressClassName: public; -
traefik-private— обрабатывает только Ingress сingressClassName: private.
Каждый экземпляр создает собственный сервис типа LoadBalancer, но с разными параметрами:
-
публичный Traefik получает внешний IP-адрес и принимает трафик из интернета;
-
приватный Traefik создает внутренний балансировщик без публичного IP и доступен только внутри приватной сети.
За счет этого можно использовать один и тот же кластер для внешних и внутренних приложений, не смешивая их точки входа.
Перед началом убедитесь, что:
-
у вас установлен helm;
-
кластер Kubernetes развернут;
-
для проверки приватного ingress у вас есть доступ в приватную сеть кластера, например через отдельный VDS в той же сети.
Подготовка конфигурации Traefik
Оба экземпляра Traefik разворачиваются из одного Helm-чарта, но используют разные values-файлы.
Конфигурация публичного Traefik
Создайте файл traefik-public-values.yaml со следующим содержимым:
fullnameOverride: traefik-public
ingressClass:
enabled: true
isDefaultClass: false
name: public
service:
enabled: true
type: LoadBalancer
annotations:
external-dns.alpha.kubernetes.io/hostname: "example.com"
external-dns.alpha.kubernetes.io/ttl: "1200"
providers:
kubernetesCRD:
enabled: true
allowCrossNamespace: false
ingressClass: public
kubernetesIngress:
enabled: true
ingressClass: public
publishedService:
enabled: true
Этот файл настраивает публичный экземпляр Traefik, который будет обрабатывать ingress-ресурсы класса public и создавать обычный LoadBalancer с внешним IP.
Здесь важно следующее:
-
ingressClass.name: public— задает имя класса, на который будут ссылаться публичные Ingress; -
providers.kubernetesCRD.ingressClassиproviders.kubernetesIngress.ingressClass— ограничивают Traefik только ресурсами своего класса; -
publishedService.enabled: true— позволяет корректно публиковать адрес ingress-сервиса.
Аннотации external-dns.alpha.kubernetes.io/* нужны только в том случае, если в кластере используется external-dns.
В значении external-dns.alpha.kubernetes.io/hostname нужно указать реальный домен или поддомен, который вы хотите направить на публичный ingress, например app.example.com.
Если вы используете external-dns, DNS-запись будет создаваться автоматически. Если external-dns в кластере нет, эти аннотации можно убрать и создать DNS-запись вручную. В нашем примере проверка будет выполняться через curl --resolve. В таком случае реальные DNS-записи не обязательны.
Конфигурация приватного Traefik
Создайте файл traefik-private-values.yaml со следующим содержимым:
fullnameOverride: traefik-private
ingressClass:
enabled: true
isDefaultClass: false
name: private
service:
enabled: true
type: LoadBalancer
annotations:
k8s.timeweb.cloud/attached-loadbalancer-no-external-ip: "true"
providers:
kubernetesCRD:
enabled: true
allowCrossNamespace: false
ingressClass: private
kubernetesIngress:
enabled: true
ingressClass: private
publishedService:
enabled: true
Этот файл настраивает приватный экземпляр Traefik, который будет обрабатывать ingress-ресурсы класса private и создавать внутренний балансировщик без публичного IP.
Ключевой параметр здесь — k8s.timeweb.cloud/attached-loadbalancer-no-external-ip: "true". Он указывает, что балансировщик должен быть внутренним и не получать внешний публичный IP.
Установка двух экземпляров Traefik
Сначала добавьте репозиторий Helm:
helm repo add traefik https://helm.traefik.io/traefik helm repo update
После этого разверните публичный экземпляр:
helm install traefik-public traefik/traefik \ -n traefik-public --create-namespace \ -f traefik-public-values.yaml
Затем разверните приватный экземпляр:
helm install traefik-private traefik/traefik \ -n traefik-private --create-namespace \ -f traefik-private-values.yaml
После установки сразу проверьте, что оба сервиса типа LoadBalancer созданы:
kubectl get svc -n traefik-public kubectl get svc -n traefik-private
Создание балансировщиков может занимать до 10 минут. Пока ресурс создается, в колонке
EXTERNAL-IPможет отображаться состояниеpending.
Ожидаемое поведение будет разным:
-
у
traefik-publicдолжен появиться внешний IP-адрес; -
у
traefik-privateвнешний IP не появится, так как он использует внутренний балансировщик.
Проверить состояние балансировщиков можно не только через kubectl, но и в панели управления Timeweb Cloud. Дождитесь завершения создания балансировщиков.
На этом же этапе полезно убедиться, что оба класса ingress зарегистрированы в кластере:
kubectl get ingressclass
В выводе должны быть классы public и private.
Публикация публичных и приватных сервисов
После развертывания двух экземпляров Traefik достаточно указывать нужный IngressClass в манифесте Ingress.
Для публичного сервиса:
spec:
ingressClassName: public
Для приватного сервиса:
spec:
ingressClassName: private
Дальше весь трафик будет маршрутизироваться через соответствующий ingress-контроллер.
Практический пример
Ниже приведен полный набор манифестов для демонстрации. В примере будут:
-
два публичных сервиса
service1иservice2; -
один приватный сервис
service3; -
два ingress-ресурса с разными IngressClass.
ConfigMap с тестовыми HTML-страницами
Создайте файл config-map.yaml:
apiVersion: v1 kind: ConfigMap metadata: name: service-config namespace: ingress-example data: service1.html: | <html> <head><title>Service 1</title></head> <body><h1>Welcome to Service 1!</h1></body> </html> service2.html: | <html> <head><title>Service 2</title></head> <body><h1>Welcome to Service 2!</h1></body> </html> service3.html: | <html> <head><title>Service 3</title></head> <body><h1>Welcome to Service 3!</h1></body> </html>
Публичный сервис service1
Создайте файл service1-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: service1
namespace: ingress-example
spec:
replicas: 2
selector:
matchLabels:
app: service1
template:
metadata:
labels:
app: service1
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /usr/share/nginx/html
volumes:
- name: config-volume
configMap:
name: service-config
items:
- key: service1.html
path: service1.html
---
apiVersion: v1
kind: Service
metadata:
name: service1
namespace: ingress-example
spec:
selector:
app: service1
ports:
- protocol: TCP
port: 80
targetPort: 80
Публичный сервис service2
Создайте файл service2-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: service2
namespace: ingress-example
spec:
replicas: 2
selector:
matchLabels:
app: service2
template:
metadata:
labels:
app: service2
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /usr/share/nginx/html
volumes:
- name: config-volume
configMap:
name: service-config
items:
- key: service2.html
path: service2.html
---
apiVersion: v1
kind: Service
metadata:
name: service2
namespace: ingress-example
spec:
selector:
app: service2
ports:
- protocol: TCP
port: 80
targetPort: 80
Приватный сервис service3
Создайте файл service3-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: service3
namespace: ingress-example
spec:
replicas: 2
selector:
matchLabels:
app: service3
template:
metadata:
labels:
app: service3
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /usr/share/nginx/html
volumes:
- name: config-volume
configMap:
name: service-config
items:
- key: service3.html
path: index.html
---
apiVersion: v1
kind: Service
metadata:
name: service3
namespace: ingress-example
spec:
selector:
app: service3
ports:
- protocol: TCP
port: 80
targetPort: 80
Публичный ingress
Создайте файл ingress-public.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-public-ingress
namespace: ingress-example
spec:
ingressClassName: public
rules:
- host: ingress1.example.com
http:
paths:
- path: /service1
pathType: Prefix
backend:
service:
name: service1
port:
number: 80
- path: /service2
pathType: Prefix
backend:
service:
name: service2
port:
number: 80
Обратите внимание, что в spec.ingressClassName указано значение public — это означает, что Ingress будет обрабатываться публичным ingress-контроллером.
Приватный ingress
Создайте файл ingress-private.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-private-ingress
namespace: ingress-example
spec:
ingressClassName: private
rules:
- host: ingress2.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service3
port:
number: 80
В spec.ingressClassName указано значение private, поэтому Ingress будет обрабатываться приватным ingress-контроллером.
Создайте неймспейс ingress-example:
kubectl create namespace ingress-example
После этого примените все манифесты:
kubectl apply -f config-map.yaml kubectl apply -f service1-deployment.yaml kubectl apply -f service2-deployment.yaml kubectl apply -f service3-deployment.yaml kubectl apply -f ingress-public.yaml kubectl apply -f ingress-private.yaml
Теперь проверьте, что деплойменты, сервисы и ingress-ресурсы созданы:
kubectl get deploy,svc,ingress -n ingress-example
В результате вы должны увидеть три сервиса, три деплоймента и два ingress-ресурса. Если какого-то ресурса нет, значит соответствующий манифест не применился или был изменен.
Запросы к ingress1.example.com/service1... будут попадать в service1, а запросы к ingress1.example.com/service2... — в service2.
Запросы к ingress2.example.com/ будут попадать в service3.
Такой сервис будет доступен только из приватной сети, если запрос приходит через внутренний балансировщик Traefik.
Проверка работы
Перед проверкой запросами полезно еще раз посмотреть, какой класс назначен каждому ingress:
kubectl describe ingress example-public-ingress -n ingress-example kubectl describe ingress example-private-ingress -n ingress-example
Убедитесь, что у example-public-ingress указан Ingress Class: public, а у example-private-ingress — Ingress Class: private. Если класс не совпадает, Traefik не будет обрабатывать такой ресурс.
В примере используются имена
ingress1.example.comиingress2.example.com. Это условные хосты. Для проверки черезcurl --resolveих можно использовать без настройки реального DNS. Если вы хотите открывать публичныйingressв браузере, заменитеingress1.example.comна реальный домен или поддомен и настройте для него DNS-запись.
Когда публичный балансировщик получит внешний IP, проверьте маршруты командами:
curl http://ingress1.example.com/service1.html \ --resolve ingress1.example.com:80:PUBLIC_LB_EXTERNAL_IP curl http://ingress1.example.com/service2.html \ --resolve ingress1.example.com:80:PUBLIC_LB_EXTERNAL_IP
Где PUBLIC_LB_EXTERNAL_IP — публичный IP, выделенный для балансировщика.
Если в ответ приходит 404, проверьте, что вы используете именно хост ingress1.example.com и пути /service1 или /service2.
Для приватного сервиса выполните проверку с машины, которая находится в той же приватной сети, что и кластер:
curl http://ingress2.example.com/ \ --resolve ingress2.example.com:80:PRIVATE_LB_IP
Где PRIVATE_LB_IP — приватный IP балансировщика.
В ответ вы должны получить HTML-страницу service3.
Если приватный маршрут не открывается, проверьте, что запрос идет из приватной сети. Внешний интернет-трафик на такой ingress не попадет, даже если сам ресурс в кластере настроен правильно.