Files
tf_provider/docs/TODO/GATEWAY_SERVICE.md
T

18 KiB
Raw Blame History

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)

  1. Terraform читает .tf:

    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, который отвечает:

    { "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-переменные)

# 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 сам разберётся:

  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.goaddress по умолчанию на новый домен
  • Обновить 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

  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. Планирование завершено, реализация — позже.