---
title: "Настройка приватных и публичных сервисов в Kubernetes"
description: "Управление трафиком приватных и публичных сервисов в Kubernetes. Документация и инструкции по использованию и настройке облачных сервисов Timeweb Cloud."
---

> Полный индекс документации для ИИ-агентов: [llms.txt](https://timeweb.cloud/llms.txt).

Если в кластере нужно публиковать часть сервисов в интернет, а часть оставлять доступной только из приватной сети, удобнее всего разделить входящий трафик на уровне ingress-контроллеров. Для этого можно развернуть два экземпляра Traefik: один будет обслуживать публичные Ingress, второй — приватные.

В этой статье разберем такой сценарий. В качестве основы будем использовать два Helm-релиза Traefik с разными `IngressClass`, а затем покажем, как маршрутизировать запросы к публичным и внутренним сервисам.

Идея в том, что в кластере запускаются два независимых экземпляра Traefik:

-   `traefik-public` — обрабатывает только Ingress с `ingressClassName: public`;
    
-   `traefik-private` — обрабатывает только Ingress с `ingressClassName: private`.
    

Каждый экземпляр создает собственный сервис типа `LoadBalancer`, но с разными параметрами:

-   публичный Traefik получает внешний IP-адрес и принимает трафик из интернета;
    
-   приватный Traefik создает внутренний балансировщик без публичного IP и доступен только внутри приватной сети.
    

За счет этого можно использовать один и тот же кластер для внешних и внутренних приложений, не смешивая их точки входа.

Перед началом убедитесь, что:

-   у вас [установлен helm](https://timeweb.cloud/docs/k8s/helm);
    
-   кластер Kubernetes развернут;
    
-   для проверки приватного ingress у вас есть [доступ в приватную сеть](https://timeweb.cloud/docs/vpc/managing-bgp-networks) кластера, например через отдельный VDS в той же сети.
    

## Подготовка конфигурации Traefik

Оба экземпляра Traefik разворачиваются из одного Helm-чарта, но используют разные values-файлы.

### Конфигурация публичного Traefik

Создайте файл `traefik-public-values.yaml` со следующим содержимым:

```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](https://timeweb.cloud/docs/k8s/addons/externaldns).

В значении `external-dns.alpha.kubernetes.io/hostname` нужно указать реальный домен или поддомен, который вы хотите направить на публичный `ingress`, например `app.example.com`.

Если вы используете `external-dns`, DNS-запись будет создаваться автоматически. Если `external-dns` в кластере нет, эти аннотации можно убрать и создать DNS-запись вручную. В нашем примере проверка будет выполняться через `curl --resolve`. В таком случае реальные DNS-записи не обязательны.

### Конфигурация приватного Traefik

Создайте файл `traefik-private-values.yaml` со следующим содержимым:

```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:

```bash
helm repo add traefik https://helm.traefik.io/traefik
helm repo update
```

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

```bash
helm install traefik-public traefik/traefik \
  -n traefik-public --create-namespace \
  -f traefik-public-values.yaml
```

Затем разверните приватный экземпляр:

```bash
helm install traefik-private traefik/traefik \
  -n traefik-private --create-namespace \
  -f traefik-private-values.yaml
```

После установки сразу проверьте, что оба сервиса типа LoadBalancer созданы:

```bash
kubectl get svc -n traefik-public
kubectl get svc -n traefik-private
```

> [!NOTE]
> Создание балансировщиков может занимать до 10 минут. Пока ресурс создается, в колонке `EXTERNAL-IP` может отображаться состояние `pending`.

Ожидаемое поведение будет разным:

-   у `traefik-public` должен появиться внешний IP-адрес;
    
-   у `traefik-private` внешний IP не появится, так как он использует внутренний балансировщик.
    

Проверить состояние балансировщиков можно не только через `kubectl`, но и в панели управления Timeweb Cloud. Дождитесь завершения создания балансировщиков.

На этом же этапе полезно убедиться, что оба класса `ingress` зарегистрированы в кластере:

```bash
kubectl get ingressclass
```

В выводе должны быть классы public и private. 

## Публикация публичных и приватных сервисов

После развертывания двух экземпляров Traefik достаточно указывать нужный `IngressClass` в манифесте Ingress.

Для публичного сервиса:

```yaml
spec:
  ingressClassName: public
```

Для приватного сервиса:

```yaml
spec:
  ingressClassName: private
```

Дальше весь трафик будет маршрутизироваться через соответствующий ingress-контроллер.

## Практический пример

Ниже приведен полный набор манифестов для демонстрации. В примере будут:

-   два публичных сервиса `service1` и `service2`;
    
-   один приватный сервис `service3`;
    
-   два ingress-ресурса с разными IngressClass.
    

#### ConfigMap с тестовыми HTML-страницами

Создайте файл `config-map.yaml`:

```shell
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`:

```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`:

```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`:

```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`:

```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`:

```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`:

```bash
kubectl create namespace ingress-example
```

После этого примените все манифесты:

```bash
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-ресурсы созданы:

```bash
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:

```bash
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 не будет обрабатывать такой ресурс.

> [!NOTE]
> В примере используются имена `ingress1.example.com` и `ingress2.example.com`. Это условные хосты. Для проверки через `curl --resolve` их можно использовать без настройки реального DNS. Если вы хотите открывать публичный `ingress` в браузере, замените `ingress1.example.com` на реальный домен или поддомен и настройте для него DNS-запись.

Когда публичный балансировщик получит внешний IP, проверьте маршруты командами:

```bash
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`. 

Для приватного сервиса выполните проверку с машины, которая находится в той же приватной сети, что и кластер:

```bash
curl http://ingress2.example.com/ \
  --resolve ingress2.example.com:80:PRIVATE_LB_IP
```

Где `PRIVATE_LB_IP` — приватный IP балансировщика.

В ответ вы должны получить HTML-страницу `service3`.

Если приватный маршрут не открывается, проверьте, что запрос идет из приватной сети. Внешний интернет-трафик на такой `ingress` не попадет, даже если сам ресурс в кластере настроен правильно.
