18 KiB
GATEWAY Service — Path-based DNS-less Router
Репозиторий:
gateway(шаблонный, создан 2026-07-20) URL:https://gateway.nodejsk8s.services.ngcloud.ru/Назначение: Замена всех DNS-записей*.kube5s.ruединым path-based роутером Статус: СПЛАНИРОВАНО, НЕ НАЧАТО
1. ЗАЧЕМ ЭТО НУЖНО
Проблема
Сейчас каждый сервис живёт на своём поддомене *.kube5s.ru:
| Поддомен | Сервис | Ingress | TLS-сертификат |
|---|---|---|---|
registry.kube5s.ru |
Terraform Registry | отдельный | отдельный |
something1.kube5s.ru |
Сервис 1 | отдельный | отдельный |
something2.kube5s.ru |
Сервис 2 | отдельный | отдельный |
| ... | ... | ... | ... |
Проблемы:
- Домен
kube5s.ru— срочный, скоро истекает - Продление — дорого
- Каждый поддомен = DNS-запись + Ingress + сертификат — администрировать заебало
Решение
Один домен, один Ingress, один сертификат. Все сервисы — под path-префиксами:
https://gateway.nodejsk8s.services.ngcloud.ru/record01/* → Сервис 1
https://gateway.nodejsk8s.services.ngcloud.ru/record02/* → Сервис 2
https://gateway.nodejsk8s.services.ngcloud.ru/record03/* → Сервис 3
Домен gateway.nodejsk8s.services.ngcloud.ru — облачный managed service, назначается облаком, не требует покупки/продления.
Выгода
| Было (DNS-based) | Стало (Path-based) |
|---|---|
| N DNS-записей | 0 дополнительных DNS-записей |
| N Ingress'ов | 1 Ingress |
| N TLS-сертификатов | 1 TLS-сертификат |
Домен kube5s.ru нужен |
Домен kube5s.ru не нужен |
2. АРХИТЕКТУРА
graph TD
subgraph "Внешний мир"
T[Terraform CLI]
C[Клиенты API]
end
subgraph "Kubernetes (облако Nubes)"
I["Ingress<br/>gateway.nodejsk8s.services.ngcloud.ru<br/>(cert-manager + LE)"]
G["GATEWAY Service<br/>(NodeJS: Express + http-proxy-middleware)"]
I --> G
G -->|"/record01/*"| R1["registry-server<br/>(Terraform Registry)"]
G -->|"/record02/*"| R2["Сервис 2<br/>(будущий)"]
G -->|"/record03/*"| R3["Сервис 3<br/>(будущий)"]
G -->|"/"| FALLBACK["Fallback / health-check"]
end
T -->|"source = gateway...nubes.ru/nubes/nubes"| I
Как это работает (для Terraform Registry)
-
Terraform читает
.tf:source = "gateway.nodejsk8s.services.ngcloud.ru/nubes-test/nubes" -
Terraform идёт в:
GET https://gateway.nodejsk8s.services.ngcloud.ru/.well-known/terraform.json -
GATEWAY проксирует на registry-server, который отвечает:
{ "providers.v1": "/record01/v1/providers/" } -
Terraform дальше сам идёт по
/record01/v1/providers/nubes-test/nubes/versions -
Registry-server генерит download-url с префиксом
/record01/v1/proxy?... -
Profit. Terraform скачивает провайдер.
3. КОНФИГУРАЦИЯ GATEWAY
Формат конфига (config.yaml или env-переменные)
# gateway/config.yaml
routes:
- id: "registry"
prefix: "/record01"
description: "Terraform Provider Registry"
upstream: "http://registry-server.terra.svc.cluster.local:8080"
stripPrefix: false # НЕ отрезать /record01 — registry сам знает свой префикс
timeout: 30000 # 30s (registry может долго листить S3)
- id: "future-service-2"
prefix: "/record02"
description: "Будущий сервис — TODO"
upstream: "http://service2.namespace.svc.cluster.local:8080"
stripPrefix: true # Отрезать /record02 перед отправкой в upstream
timeout: 10000
- id: "future-service-3"
prefix: "/record03"
description: "Будущий сервис — TODO"
upstream: "http://service3.namespace.svc.cluster.local:3000"
stripPrefix: true
timeout: 10000
Конфиг через env (альтернатива, проще для K8s)
# Формат: ROUTE_XX_ID, ROUTE_XX_PREFIX, ROUTE_XX_UPSTREAM, ROUTE_XX_STRIP, ROUTE_XX_TIMEOUT
ROUTE_01_ID=registry
ROUTE_01_PREFIX=/record01
ROUTE_01_UPSTREAM=http://registry-server.terra.svc.cluster.local:8080
ROUTE_01_STRIP=false
ROUTE_01_TIMEOUT=30000
ROUTE_02_ID=future-svc
ROUTE_02_PREFIX=/record02
ROUTE_02_UPSTREAM=http://service2.namespace.svc.cluster.local:8080
ROUTE_02_STRIP=true
ROUTE_02_TIMEOUT=10000
Важно: stripPrefix
| Режим | Когда использовать |
|---|---|
stripPrefix: false |
Upstream знает о своём префиксе и сам его обрабатывает (как registry-server с PATH_PREFIX=/record01) |
stripPrefix: true |
Upstream не знает о префиксе — GATEWAY отрезает его перед проксированием. /record01/api/users → /api/users |
4. КОД GATEWAY (план)
Стек
- NodeJS + Express — HTTP-фреймворк
- http-proxy-middleware — реверс-прокси
- Dotenv / js-yaml — конфигурация
Структура проекта
gateway/
├── package.json
├── server.js # Главный файл
├── config/
│ ├── routes.yaml # Конфиг маршрутов
│ └── index.js # Загрузчик конфига
├── lib/
│ ├── proxy.js # Фабрика proxy-middleware'ов из конфига
│ ├── health.js # Health/readiness probes
│ └── logger.js # Логирование
├── Dockerfile
├── .dockerignore
└── README.md
server.js (псевдокод)
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const { loadRoutes } = require('./config');
const { healthHandler, readyHandler } = require('./lib/health');
const app = express();
const routes = loadRoutes(); // из env или routes.yaml
// Health probes (без прокси)
app.get('/healthz', healthHandler);
app.get('/readyz', readyHandler);
// Root — информационная страница
app.get('/', (req, res) => {
res.json({
service: 'gateway',
version: process.env.APP_VERSION || '1.0.0',
routes: routes.map(r => ({
prefix: r.prefix,
description: r.description
}))
});
});
// Динамическая регистрация прокси-маршрутов
for (const route of routes) {
const proxy = createProxyMiddleware({
target: route.upstream,
changeOrigin: true,
pathRewrite: route.stripPrefix
? { [`^${route.prefix}`]: '' }
: undefined,
timeout: route.timeout,
onError: (err, req, res) => {
console.error(`[${route.id}] Proxy error:`, err.message);
res.status(502).json({ error: 'Upstream unavailable', route: route.id });
}
});
app.use(route.prefix, proxy);
console.log(`Route: ${route.prefix} → ${route.upstream} (strip: ${route.stripPrefix})`);
}
// 404 для всего остального
app.use((req, res) => {
res.status(404).json({ error: 'Not Found', path: req.path });
});
app.listen(process.env.PORT || 3000);
5. КАК ДОБАВИТЬ НОВЫЙ СЕРВИС (RECORD)
Шаг 1: Создать сервис в K8s
# Пример: новый сервис my-api
apiVersion: v1
kind: Service
metadata:
name: my-api
namespace: default
spec:
ports:
- port: 8080
selector:
app: my-api
Шаг 2: Добавить route в конфиг gateway
# Добавить в config/routes.yaml:
- id: "my-api"
prefix: "/record04"
description: "Мой новый API"
upstream: "http://my-api.default.svc.cluster.local:8080"
stripPrefix: true
timeout: 10000
Шаг 3: Передеплоить gateway
kubectl rollout restart deployment/gateway -n terra
Шаг 4: Готово
Сервис доступен по адресу:
https://gateway.nodejsk8s.services.ngcloud.ru/record04/
Никаких DNS, Ingress'ов, сертификатов. Только конфиг gateway.
6. ЧТО НУЖНО ПОМЕНЯТЬ В REGISTRY-SERVER
Файл: ~/tf_registry/server/main.go
Добавить env-переменную
var pathPrefix = os.Getenv("PATH_PREFIX") // "/record01" или "" (пустая строка = без префикса, обратная совместимость)
Изменить main() — регистрация handlers
// БЫЛО:
http.HandleFunc("/.well-known/terraform.json", discoveryHandler)
http.HandleFunc("/v1/providers/", router)
http.HandleFunc("/v1/proxy", proxyHandler)
http.HandleFunc("/docs/", docsHandler)
// СТАЛО:
http.HandleFunc(pathPrefix+"/.well-known/terraform.json", discoveryHandler)
http.HandleFunc(pathPrefix+"/v1/providers/", router)
http.HandleFunc(pathPrefix+"/v1/proxy", proxyHandler)
http.HandleFunc(pathPrefix+"/docs/", docsHandler)
Изменить discoveryHandler() — ответ с префиксом
// БЫЛО:
json.NewEncoder(w).Encode(Discovery{ProvidersV1: "/v1/providers/"})
// СТАЛО:
json.NewEncoder(w).Encode(Discovery{ProvidersV1: pathPrefix + "/v1/providers/"})
Изменить downloadVersion() — ссылки с префиксом
// БЫЛО:
baseURL := "https://" + hostname
downloadLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(fullKey))
// СТАЛО:
baseURL := "https://" + hostname + pathPrefix
downloadLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(fullKey))
Изменить listVersions() — S3-префикс
// БЫЛО:
prefix := fmt.Sprintf("%s/%s/%s/", hostname, namespace, pType)
// СТАЛО (hostname теперь = gateway.nodejsk8s.services.ngcloud.ru):
// Если PATH_PREFIX непустой, можно использовать его как основу для S3-пути,
// либо продолжать использовать hostname. Зависит от того, как лежат файлы в S3.
// Пока оставляем hostname — он просто поменяется на новый домен.
prefix := fmt.Sprintf("%s/%s/%s/", hostname, namespace, pType)
Новые env-переменные для registry-server
| Переменная | Было | Стало |
|---|---|---|
REGISTRY_HOSTNAME |
terra.k8c.ru / registry.kube5s.ru |
gateway.nodejsk8s.services.ngcloud.ru |
PATH_PREFIX |
— | /record01 |
7. КАК ЭТО ВЫГЛЯДИТ ДЛЯ КЛИЕНТОВ (terraform init)
Было
terraform {
required_providers {
nubes = {
source = "registry.kube5s.ru/nubes-test/nubes"
version = "5.1.13"
}
}
}
Стало
terraform {
required_providers {
nubes = {
source = "gateway.nodejsk8s.services.ngcloud.ru/nubes-test/nubes"
version = "5.1.13"
}
}
}
Всё. Terraform сам разберётся:
- Пойдёт на
.well-known/terraform.json - Получит путь
/record01/v1/providers/ - Скачает провайдер через
/record01/v1/proxy?...
8. ПЛАН ВНЕДРЕНИЯ
Фаза 1: Подготовка GATEWAY
- Склонировать шаблонный репо
- Написать
server.js(прокси-роутер) - Написать
config/routes.yamlс первым роутом (/record01→ registry) - Dockerfile
- Helm chart / K8s manifests (Deployment, Service)
- Ingress:
gateway.nodejsk8s.services.ngcloud.ru→ gateway-service - TLS через cert-manager
Фаза 2: Подготовка Registry-Server
- Добавить
PATH_PREFIXвserver/main.go - Изменить
REGISTRY_HOSTNAMEнаgateway.nodejsk8s.services.ngcloud.ru - Собрать новый Docker-образ
- Задеплоить registry-server с новыми env
- Убедиться что registry отвечает на
/record01/.well-known/terraform.json - Убедиться что download-url содержат
/record01/
Фаза 3: Тестирование
curl https://gateway.nodejsk8s.services.ngcloud.ru/record01/.well-known/terraform.json→ OKcurl https://gateway.nodejsk8s.services.ngcloud.ru/record01/v1/providers/nubes-test/nubes/versions→ OKterraform initс новым source → OK
Фаза 4: Миграция клиентов
- Обновить
provider/main.go—addressпо умолчанию на новый домен - Обновить profile.env (dev/test/prod) —
REGISTRY_HOSTNAME - Обновить все
.tfфайлы вPROD_STAND/,TEST_STAND/,DEV_STAND/ - Обновить CI/CD (
.github/workflows/, build scripts) - Старый
registry.kube5s.ru— 301 редирект или удалить
Фаза 5: Добавление остальных сервисов
- Выявить все текущие
*.kube5s.ru(через grep по всем репо) - Для каждого: добавить route в gateway
- Перенести сервис за gateway
- Удалить старые DNS/Ingress/сертификаты
9. ТЕКУЩИЕ ЗАПИСИ kube5s.ru (НАЙТИ ВСЕ)
# Выполнить чтобы найти все использования kube5s.ru:
grep -r "kube5s\.ru" /home/naeel/ --include="*.{tf,yaml,yml,go,sh,env,js,json,md,cfg,ejs,html}" -l
Известные на 2026-07-20:
| Запись | Сервис | Приоритет миграции |
|---|---|---|
registry.kube5s.ru |
Terraform Registry Server | #1 (критично) |
| ??? | ??? | Найти grep'ом |
10. КОНФИГУРАЦИЯ KUBERNETES
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: gateway
namespace: terra
spec:
replicas: 1
selector:
matchLabels:
app: gateway
template:
metadata:
labels:
app: gateway
spec:
containers:
- name: gateway
image: registry.kube5s.ru/gateway:1.0.0
ports:
- containerPort: 3000
env:
- name: PORT
value: "3000"
- name: APP_VERSION
value: "1.0.0"
# ROUTES
- name: ROUTE_01_ID
value: "registry"
- name: ROUTE_01_PREFIX
value: "/record01"
- name: ROUTE_01_UPSTREAM
value: "http://registry-server.terra.svc.cluster.local:8080"
- name: ROUTE_01_STRIP
value: "false"
- name: ROUTE_01_TIMEOUT
value: "30000"
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 5
readinessProbe:
httpGet:
path: /readyz
port: 3000
initialDelaySeconds: 3
Service
apiVersion: v1
kind: Service
metadata:
name: gateway
namespace: terra
spec:
ports:
- port: 80
targetPort: 3000
selector:
app: gateway
Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: gateway-ingress
namespace: terra
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
spec:
ingressClassName: nginx
tls:
- hosts:
- gateway.nodejsk8s.services.ngcloud.ru
secretName: gateway-tls
rules:
- host: gateway.nodejsk8s.services.ngcloud.ru
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: gateway
port:
number: 80
11. ССЫЛКИ
| Ресурс | URL |
|---|---|
| GATEWAY (production) | https://gateway.nodejsk8s.services.ngcloud.ru/ |
| Репозиторий GATEWAY | ~/tf_provider/gateway/ (создать) или отдельный репо |
| Registry Server | ~/tf_registry/server/main.go |
| Terraform Provider | ~/tf_provider/provider/main.go |
| S3 (registry files) | s3.msk-1.ngcloud.ru, bucket terraform-registry |
12. КОНТРОЛЬНЫЙ СПИСОК ПРИ ДОБАВЛЕНИИ НОВОГО RECORD
- Сервис задеплоен в K8s, есть Service
- Сервис работает (проверить через
kubectl port-forwardили напрямую) - Определён
stripPrefix: upstream знает о префиксе? (как registry) или нет? - Добавлен route в конфиг gateway
- Gateway передеплоен
- Проверен curl через gateway:
curl https://gateway...ngcloud.ru/recordXX/... - Клиенты обновлены на новый URL
Документ создан 2026-07-20. Планирование завершено, реализация — позже.