mod: проксирование через traefik

This commit is contained in:
Sergei Erjemin 2025-04-22 15:26:28 +03:00
parent 3bc414ca98
commit e2c18fd572

View File

@ -1,100 +1,216 @@
# Проксирование внешнего хоста через Traefik (Ingress-контроллер)
У меня в домашней сети есть хост с сервисом (audiobookshelf), то удобно его прокинуть через Traefik, чтобы не открывать
на внешний IP лишних адресов, портов, использовать домен и включить SSL, и управлять всем этим через из единой
точки.
У меня в домашней сети есть хост с web-сервисом (audiobookshelf), а стандартные web-порты (80 для HTTP и 443 для HTTPS)
на домашнем роутере перенаправлены в кластер k3S (через keepalived). Таким образом, если прокинуть http-трафик этого
хоста через Traefik, то можно будет получить доступ к этому сервису через доменное имя и SSL-сертификат от
Lets Encrypt.
Для удобства я поместил все манифесты в один файл (вы можете оформить из и как отдельные файлы). Так как хотелось бы
описать как делаются универсальные манифесты, которые можно использовать для проксирования любого сервиса, то
я заменил в нем конкретные значения на "заглушки. Можно взять этот манифест и просто заменить в нем значения на
свои:
В моем случае:
* `<PROXIED-HOST>` -- IP-адрес хоста, где работает сервис, который надо проксировать.
* `<PROXIED-PORT>` -- порт, с которого отвечает сервис.
* `<YOU-DOMAIN-NAME>` -- доменное имя, на которое будет проксировать сервис.
* `<NAME-SPACE>` -- пространство имен кластера, в котором будет создан сервис, маршруты, секреты и все необходимое
для проксирования. Пространство имен -- это логическая группа ресурсов в кластере Kubernetes, которая позволяет
организовать и изолировать ресурсы.
* `<SERVICE-NAME>` -- имя сервиса, который будет проксироваться. Это имя, для простоты, будем использоваться
* и в маршрутах, и сертификатах, и в секрете...
## Пространство имен
Чтобы все было аккуратно и серисы и поды не путались, создадим пространство имен для проксируемого сервиса.
Например, `ab-shelf`.
Чтобы все было аккуратно и сервисы и поды не путались, создадим пространство имен для проксирования конкретного хоста.
Мо хост относится к <SERVICE-NAME>, поэтому назову пространство имен `<NAME-SPACE>`
Например, `<NAME-SPACE>`.
```bash
sudo ubectl create namespace ab-shelf
sudo ubectl create namespace <NAME-SPACE>
```
Проверяем, что пространство создано:
```bash
sudo kubectl get namespace ab-shelf
sudo kubectl get namespace <NAME-SPACE>
```
Увидим, что пространство создано и активно:
```text
NAME STATUS AGE
ab-shelf Active 54s
<NAME-SPACE> Active 54s
```
## Конфигурация IngressRoute, Service и Endpoints
## Конфигурация всего
Для удобства я объединил манифесты в один файл (но можно и по отдельности). Создаем единый манифетст:
Для удобства я объединил манифесты в один файл (но можно и по отдельности). Создаем единый манифест:
```bash
sudo nano ~/k3s/audiobookshelf/audiobookshelf.yaml
sudo nano ~/k3s/<SERVICE-NAME>/<SERVICE-NAME>.yaml
```
И вставляем в него следующее содержимое (не забудь заменить `<PROXIED-HOST>`, `<PROXIED-PORT>` и `<YOU-DOMAIN-NAME>`
на свои значения... пространство имен `ab-shelf` и имя сервиса `audiobookshelf` можно тоже поменять на свое, если
у вас другой сервис):
Он состоит из следующих частей:
* `Endpoints` -- указывает на конечную точку, в которую будет проксироваться трафик. В данном случае это
IP-адрес и порт, на который будет проксироваться запрос.
* `Service` -- создает сервис, который будет использоваться для проксирования запросов к `Endpoints`.
* `Certificate` -- создает сертификат для домена `<YOU-DOMAIN-NAME>`, который будет использоваться для
шифрования трафика. Сертификат запрашивается через `cert-manager`, который автоматически обновляет его по мере
необходимости.
* `Middleware` -- создает промежуточное ПО, которое будет использоваться для обработки запросов. В данном случае
это редирект с HTTP на HTTPS и исключение редиректа для ACME challenge (механизма внешней проверки владения доменом
со стороны Lets Encrypt).
* `IngressRoute` -- создает маршрут, который будет использоваться для проксирования запросов к сервису.
В данном случае это маршруты для HTTP и HTTPS, которые будут обрабатывать запросы на домен `<YOU-DOMAIN-NAME>`.
Также создается маршрут для ACME challenge, который позволяет cert-manager пройти проверку через порт 80.
Вставляем в манифест следующее содержимое (не забудьте заменить `<PROXIED-HOST>`, `<PROXIED-PORT>`, `<YOU-DOMAIN-NAME>`,
`<NAME-SPACE>` и `<SERVICE-NAME>` на свои значения):
```yaml
# Endpoints для внешнего хоста
# Endpoints для внешнего хоста <SERVICE-NAME>
# Задаёт IP и порт внешнего сервера, так как <SERVICE-NAME> внешний хост для k3s
apiVersion: v1
kind: Endpoints
metadata:
name: audiobookshelf
namespace: ab-shelf # пространство, например
subsets:
name: <SERVICE-NAME>
namespace: <NAME-SPACE> # Namespace для <SERVICE-NAME>
subsets: # Прямо в корне, без spec
- addresses:
- ip: <PROXIED-HOST>
- ip: <PROXIED-HOST> # IP Synology, где работает <SERVICE-NAME>
ports:
- port: <PROXIED-PORT>
- port: <PROXIED-PORT> # Порт Synology (HTTP)
protocol: TCP
---
# Service для проксируемого хоста (<PROXIED-HOST>:<PROXIED-PORT>)
---
# Service для маршрутизации трафика от Traefik к внешнему хосту
# Связывает IngressRoute с Endpoints
apiVersion: v1
kind: Service
metadata:
name: audiobookshelf
namespace: ab-shelf # пространство, например
name: <SERVICE-NAME>
namespace: <NAME-SPACE>
spec:
ports:
- port: <PROXIED-PORT>
targetPort: <PROXIED-PORT>
- port: <PROXIED-PORT> # Порт сервиса, на который Traefik отправляет трафик
targetPort: <PROXIED-PORT> # Порт на Synology
protocol: TCP
---
# IngressRoute
---
# Certificate для TLS-сертификата от Lets Encrypt
# Запрашивает сертификат для <YOU-DOMAIN-NAME> через cert-manager
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 для Lets Encrypt
kind: ClusterIssuer
---
# Middleware для редиректа HTTP → HTTPS
# Применяется к HTTP-запросам для перенаправления на HTTPS
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
# Позволяет Lets Encrypt проверять /.well-known/acme-challenge без HTTPS
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.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: audiobookshelf
namespace: ab-shelf # пространство, например
name: <SERVICE-NAME>-http
namespace: <NAME-SPACE>
spec:
entryPoints:
- web-custom # ендпоинт, который "слушает" порт 2055
- 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.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: audiobookshelf
- name: <SERVICE-NAME>
port: <PROXIED-PORT>
tls:
secretName: <SERVICE-NAME>-tls # Сертификат от cert-manager
---
# IngressRoute для HTTP-01 challenge (Lets Encrypt)
# Позволяет cert-manager пройти проверку через порт 80
apiVersion: traefik.containo.us/v1alpha1
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
```
Что тут происходит:
* `Endpoints` -- указываем, что сервис будет проксировать запросы на внешний хост `<PROXIED-HOST>`
и порт `<PROXIED-PORT>`. Ендпоинт -- это конечная точка, к которой будет проксироваться запрос. В данном случае
это IP-адрес и порт, но обычно это имя пода, на который отправляются и который отвечает на запросы.
* `Service` -- создаем сервис, который будет использоваться для проксирования запросов к `Endpoints`. Сервис -- это
абстракция, которая позволяет упрощать доступ к подам. Он может использоваться для балансировки нагрузки между
несколькими потоками, которые обрабатывают запросы. В данном случае мы создаем сервис, который будет проксировать
запросы к `Endpoints` (внешнему хосту).
* `IngressRoute` -- создаем маршрут, который будет проксировать запросы на домен `<YOU-DOMAIN-NAME>`
к сервису `audiobookshelf` в пространстве `ab-shelf`. Маршрут -- это правило, которое определяет, как обрабатывать
запросы, поступающие на определенный адрес. В данном случае мы создаем маршрут, который будет направлять запросы
на домен `<YOU-DOMAIN-NAME>` к сервису `audiobookshelf` в пространстве `ab-shelf`. Внутри маршрута мы указываем,
что запросы должны обрабатываться сервисом `audiobookshelf` на порту `<PROXIED-PORT>`.
В данном случае мы используем `web-custom` как точку входа, которая будет слушать порт 2055.
ВАЖНО: В манифесте используется letsencrypt-prod для получения сертификата от Lets Encrypt. Это нестандартный
ClusterIssuer cert-manager, создание которого описано в [документации](https://cert-manager.io/docs/usage/ingress/#tls-termination)
и [отдельной инструкции](k3s-custom-container-deployment.md#создание-clusterissuer)
(возможно, вам нужно будет создать его отдельно). Если вы используете другой ClusterIssuer, то замените letsencrypt-prod
на имя вашего ClusterIssuer в секции `issuerRef` в манифесте.
## Применяем манифест
```bash
sudo kubectl apply -f ~/k3s/<SERVICE-NAME>/<SERVICE-NAME>.yaml
```
Проверяем, что все ресурсы создались:
```bash
sudo kubectl get all -n <NAME-SPACE>
```
Проверяем, что сертификат создан:
```bash
sudo kubectl describe certificate -n <NAME-SPACE> <SERVICE-NAME>-tls
```