Files
tf_provider/docs/TODO/GATEWAY_SERVICE.md
T

575 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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. АРХИТЕКТУРА
```mermaid
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)
1. Terraform читает `.tf`:
```hcl
source = "gateway.nodejsk8s.services.ngcloud.ru/nubes-test/nubes"
```
2. Terraform идёт в:
```
GET https://gateway.nodejsk8s.services.ngcloud.ru/.well-known/terraform.json
```
3. GATEWAY проксирует на registry-server, который отвечает:
```json
{ "providers.v1": "/record01/v1/providers/" }
```
4. Terraform дальше сам идёт по `/record01/v1/providers/nubes-test/nubes/versions`
5. Registry-server генерит download-url с префиксом `/record01/v1/proxy?...`
6. Profit. Terraform скачивает провайдер.
---
## 3. КОНФИГУРАЦИЯ GATEWAY
### Формат конфига (`config.yaml` или env-переменные)
```yaml
# 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)
```bash
# Формат: 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` (псевдокод)
```javascript
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
```yaml
# Пример: новый сервис my-api
apiVersion: v1
kind: Service
metadata:
name: my-api
namespace: default
spec:
ports:
- port: 8080
selector:
app: my-api
```
### Шаг 2: Добавить route в конфиг gateway
```yaml
# Добавить в 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
```bash
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-переменную
```go
var pathPrefix = os.Getenv("PATH_PREFIX") // "/record01" или "" (пустая строка = без префикса, обратная совместимость)
```
### Изменить `main()` — регистрация handlers
```go
// БЫЛО:
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()` — ответ с префиксом
```go
// БЫЛО:
json.NewEncoder(w).Encode(Discovery{ProvidersV1: "/v1/providers/"})
// СТАЛО:
json.NewEncoder(w).Encode(Discovery{ProvidersV1: pathPrefix + "/v1/providers/"})
```
### Изменить `downloadVersion()` — ссылки с префиксом
```go
// БЫЛО:
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-префикс
```go
// БЫЛО:
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)
### Было
```hcl
terraform {
required_providers {
nubes = {
source = "registry.kube5s.ru/nubes-test/nubes"
version = "5.1.13"
}
}
}
```
### Стало
```hcl
terraform {
required_providers {
nubes = {
source = "gateway.nodejsk8s.services.ngcloud.ru/nubes-test/nubes"
version = "5.1.13"
}
}
}
```
**Всё.** Terraform сам разберётся:
1. Пойдёт на `.well-known/terraform.json`
2. Получит путь `/record01/v1/providers/`
3. Скачает провайдер через `/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` → OK
- [ ] `curl https://gateway.nodejsk8s.services.ngcloud.ru/record01/v1/providers/nubes-test/nubes/versions` → OK
- [ ] `terraform 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` (НАЙТИ ВСЕ)
```bash
# Выполнить чтобы найти все использования 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
```yaml
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
```yaml
apiVersion: v1
kind: Service
metadata:
name: gateway
namespace: terra
spec:
ports:
- port: 80
targetPort: 3000
selector:
app: gateway
```
### Ingress
```yaml
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
1. [ ] Сервис задеплоен в K8s, есть Service
2. [ ] Сервис работает (проверить через `kubectl port-forward` или напрямую)
3. [ ] Определён `stripPrefix`: upstream знает о префиксе? (как registry) или нет?
4. [ ] Добавлен route в конфиг gateway
5. [ ] Gateway передеплоен
6. [ ] Проверен curl через gateway: `curl https://gateway...ngcloud.ru/recordXX/...`
7. [ ] Клиенты обновлены на новый URL
---
*Документ создан 2026-07-20. Планирование завершено, реализация — позже.*