13 KiB
Проксирование внешнего хоста через Traefik (Ingress-контроллер)
У меня в домашней сети есть хост с web-сервисом (audiobookshelf), а стандартные web-порты (80 для HTTP и 443 для HTTPS) на домашнем роутере перенаправлены в кластер k3S (через keepalived). Таким образом, если прокинуть http-трафик этого хоста через Traefik, то можно будет получить доступ к этому сервису через доменное имя и SSL-сертификат от Let’s Encrypt.
Для удобства я поместил все манифесты в один файл (вы можете оформить из и как отдельные файлы). Так как хотелось бы описать как делаются универсальные манифесты, которые можно использовать для проксирования любого сервиса, то я заменил в нем конкретные значения на "заглушки. Можно взять этот манифест и просто заменить в нем значения на свои:
<PROXIED-HOST>
-- IP-адрес хоста, где работает сервис, который надо проксировать.<PROXIED-PORT>
-- порт, с которого отвечает сервис.<YOU-DOMAIN-NAME>
-- доменное имя, на которое будет проксировать сервис.<NAME-SPACE>
-- пространство имен кластера, в котором будет создан сервис, маршруты, секреты и все необходимое для проксирования. Пространство имен -- это логическая группа ресурсов в кластере Kubernetes, которая позволяет организовать и изолировать ресурсы.<SERVICE-NAME>
-- имя сервиса, который будет проксироваться. Это имя, для простоты, будем использоваться- и в маршрутах, и сертификатах, и в секрете...
Пространство имен
Чтобы все было аккуратно и сервисы и поды не путались, создадим пространство имен для проксирования конкретного хоста.
Мо хост относится к , поэтому назову пространство имен <NAME-SPACE>
Например, <NAME-SPACE>
.
sudo ubectl create namespace <NAME-SPACE>
Проверяем, что пространство создано:
sudo kubectl get namespace <NAME-SPACE>
Увидим, что пространство создано и активно:
NAME STATUS AGE
<NAME-SPACE> Active 54s
Конфигурация всего
Для удобства я объединил манифесты в один файл (но можно и по отдельности). Создаем единый манифест:
sudo nano ~/k3s/<SERVICE-NAME>/<SERVICE-NAME>.yaml
Он состоит из следующих частей:
Endpoints
-- указывает на конечную точку, в которую будет проксироваться трафик. В данном случае это IP-адрес и порт, на который будет проксироваться запрос.Service
-- создает сервис, который будет использоваться для проксирования запросов кEndpoints
.Certificate
-- создает сертификат для домена<YOU-DOMAIN-NAME>
, который будет использоваться для шифрования трафика. Сертификат запрашивается черезcert-manager
, который автоматически обновляет его по мере необходимости.Middleware
-- создает промежуточное ПО, которое будет использоваться для обработки запросов. В данном случае это редирект с HTTP на HTTPS и исключение редиректа для ACME challenge (механизма внешней проверки владения доменом со стороны Let’s Encrypt).IngressRoute
-- создает маршрут, который будет использоваться для проксирования запросов к сервису. В данном случае это маршруты для HTTP и HTTPS, которые будут обрабатывать запросы на домен<YOU-DOMAIN-NAME>
. Также создается маршрут для ACME challenge, который позволяет cert-manager пройти проверку через порт 80.
Вставляем в манифест следующее содержимое (не забудьте заменить <PROXIED-HOST>
, <PROXIED-PORT>
, <YOU-DOMAIN-NAME>
,
<NAME-SPACE>
и <SERVICE-NAME>
на свои значения):
# Endpoints для внешнего хоста <SERVICE-NAME>
# Задаёт IP и порт внешнего сервера, так как <SERVICE-NAME> внешний хост для k3s
apiVersion: v1
kind: Endpoints
metadata:
name: <SERVICE-NAME>
namespace: <NAME-SPACE> # Namespace для <SERVICE-NAME>
subsets: # Прямо в корне, без spec
- addresses:
- ip: <PROXIED-HOST> # IP Synology, где работает <SERVICE-NAME>
ports:
- port: <PROXIED-PORT> # Порт Synology (HTTP)
protocol: TCP
---
# Service для маршрутизации трафика от Traefik к внешнему хосту
# Связывает IngressRoute с Endpoints
apiVersion: v1
kind: Service
metadata:
name: <SERVICE-NAME>
namespace: <NAME-SPACE>
spec:
ports:
- port: <PROXIED-PORT> # Порт сервиса, на который Traefik отправляет трафик
targetPort: <PROXIED-PORT> # Порт на Synology
protocol: TCP
---
# Middleware для редиректа HTTP → HTTPS
# Применяется к HTTP-запросам для перенаправления на HTTPS
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
# apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: https-redirect
namespace: <NAME-SPACE>
spec:
redirectScheme:
scheme: https # Перенаправлять на HTTPS
permanent: true # Код 301 (постоянный редирект)
---
# Middleware для исключения редиректа на ACME challenge
# Позволяет Let’s Encrypt проверять /.well-known/acme-challenge без HTTPS
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
# apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: no-https-redirect
namespace: <NAME-SPACE>
spec:
stripPrefix:
prefixes:
- /.well-known/acme-challenge # Убрать префикс для ACME
---
# IngressRoute для HTTP (порт 80) с редиректом на HTTPS
# Обрабатывает HTTP-запросы и перенаправляет их
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
# apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: <SERVICE-NAME>-http
namespace: <NAME-SPACE>
spec:
entryPoints:
- web # Порт 80 (стандартный HTTP)
routes:
- match: Host("<YOU-DOMAIN-NAME>") # Запросы к <YOU-DOMAIN-NAME>
kind: Rule
services:
- name: <SERVICE-NAME> # Сервис <SERVICE-NAME>
port: <PROXIED-PORT> # Порт сервиса
middlewares:
- name: https-redirect # Редирект на HTTPS
---
# IngressRoute для HTTPS (порт 443)
# Обрабатывает HTTPS-запросы с TLS
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
# apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: <SERVICE-NAME>-https
namespace: <NAME-SPACE>
spec:
entryPoints:
- websecure # Порт 443 (HTTPS)
routes:
- match: Host("<YOU-DOMAIN-NAME>")
kind: Rule
services:
- name: <SERVICE-NAME>
port: <PROXIED-PORT>
tls:
secretName: <SERVICE-NAME>-tls # Сертификат от cert-manager
---
# IngressRoute для HTTP-01 challenge (Let’s Encrypt)
# Позволяет cert-manager пройти проверку через порт 80
apiVersion: traefik.io/v1alpha1 # версия Traefik v34.2.1+up34.2.0 (Traefik v3.3.6)
# apiVersion: traefik.containo.us/v1alpha1 # старая версия Traefik
kind: IngressRoute
metadata:
name: <SERVICE-NAME>-acme
namespace: <NAME-SPACE>
spec:
entryPoints:
- web # Порт 80
routes:
- match: Host("<YOU-DOMAIN-NAME>") && PathPrefix("/.well-known/acme-challenge")
kind: Rule
services:
- name: <SERVICE-NAME>
port: <PROXIED-PORT>
middlewares:
- name: no-https-redirect # Не редиректить ACME
---
# Certificate для TLS-сертификата от Let’s Encrypt
# Запрашивает сертификат для <YOU-DOMAIN-NAME> через cert-manager
# ВАЖНО: cert-manager должен быть установлен в кластере
# ВАЖНО: если манифесты принимаются (apply) последовательно, то манифест с сертификатом должен быть последним для
# избежания исчерпания лимитов Let’s Encrypt (пять запросов в неделю)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: <SERVICE-NAME>-tls
namespace: <NAME-SPACE>
spec:
secretName: <SERVICE-NAME>-tls # Имя секрета для сертификата
dnsNames:
- <YOU-DOMAIN-NAME> # Домен для сертификата
issuerRef:
name: letsencrypt-prod # ClusterIssuer для Let’s Encrypt
kind: ClusterIssuer
ВАЖНО: В манифесте используется letsencrypt-prod для получения сертификата от Let’s Encrypt. Это нестандартный
ClusterIssuer cert-manager, создание которого описано в документации
и отдельной инструкции
(возможно, вам нужно будет создать его отдельно). Если вы используете другой ClusterIssuer, то замените letsencrypt-prod
на имя вашего ClusterIssuer в секции issuerRef
в манифесте.
Применяем манифест
sudo kubectl apply -f ~/k3s/<SERVICE-NAME>/<SERVICE-NAME>.yaml
Проверяем, что все ресурсы создались:
sudo kubectl get all -n <NAME-SPACE>
sudo kubectl get ingressroute -n <NAME-SPACE>
sudo kubectl get middleware -n <NAME-SPACE>
sudo kubectl get service -n <NAME-SPACE>
sudo kubectl get endpoints -n <NAME-SPACE>
Увидим что-то вроде:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/<SERVICE-NAME> ClusterIP 10.43.152.59 <none> <PROXIED-PORT>/TCP 3h
Для IngressRoute:
NAME AGE
<SERVICE-NAME> 3h
<SERVICE-NAME>-acme 1h
<SERVICE-NAME>-http 1h
<SERVICE-NAME>-https 1h
Для Middleware:
NAME AGE
https-redirect 1h
no-https-redirect 1h
Для Service:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
<SERVICE-NAME> ClusterIP 10.43.152.59 <none> <PROXIED-PORT>/TCP 3h
Для Endpoints:
NAME ENDPOINTS AGE
<SERVICE-NAME> <PROXIED-HOST>:<PROXIED-PORT> 3h
Проверяем, что сертификат создан:
sudo kubectl describe certificate -n <NAME-SPACE> <SERVICE-NAME>-tls
sudo kubectl get secret -n <NAME-SPACE> <SERVICE-NAME>-tls