chore: hide WIP examples, update README
This commit is contained in:
parent
cf5ebc0b70
commit
82c9e44b1a
5
.gitignore
vendored
5
.gitignore
vendored
@ -66,3 +66,8 @@ POSTGRES/chaos_marathon.sh
|
||||
POSTGRES/test_cache_matrix.sh
|
||||
POSTGRES/deploy_and_run_chaos.sh
|
||||
POSTGRES/scripts/
|
||||
|
||||
# ---- Примеры в разработке (временно скрыты) ----
|
||||
POSTGRES/
|
||||
NODEJS/
|
||||
DEVfromGround/
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
// 2026-03-26 — main.tf: провайдер Nubes для DEV-стенда.
|
||||
// DEV API endpoint: https://deck-api-dev.ngcloud.ru/api/v1
|
||||
// Токен: secrets/dev.token (tazet@narod.ru)
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "terra.k8c.ru/nubes/nubes"
|
||||
version = "5.0.31"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "api_token" {
|
||||
type = string
|
||||
sensitive = true
|
||||
description = "Nubes API токен (DEV-стенд). Значение — в terraform.tfvars."
|
||||
}
|
||||
|
||||
variable "resource_realm" {
|
||||
type = string
|
||||
description = "Платформа развёртывания (например k8s-3.ext.nubes.ru). Уточнить у сервис-менеджера."
|
||||
}
|
||||
|
||||
provider "nubes" {
|
||||
api_token = var.api_token
|
||||
api_endpoint = "https://deck-api-dev.ngcloud.ru/api/v1/index.cfm"
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
// 2026-03-26 — vc_org.tf: ресурс «Организация в Cloud Director» для DEV-стенда.
|
||||
// nubes_vc_org — тенант vCloud Director (organization_type = "iaas").
|
||||
// resource_realm задаётся через переменную (terraform.tfvars или -var).
|
||||
|
||||
resource "nubes_vc_org" "dev_org" {
|
||||
resource_name = "vcOrg-2"
|
||||
resource_realm = var.resource_realm
|
||||
|
||||
# organization_type "iaas" — единственный вариант с доступом к организации.
|
||||
# Значение по умолчанию "iaas", явно прописано для читаемости.
|
||||
organization_type = "iaas"
|
||||
|
||||
# v_i_p_configure — JSON-список ipSpaces для операции modify.
|
||||
# При create провайдер не передаёт его в API, но требует non-null значение в плане.
|
||||
v_i_p_configure = ""
|
||||
|
||||
# adopt_existing_on_create = true — берёт существующий инстанс (dev-org-sless-demo уже создан с null realm от предыдущей попытки).
|
||||
adopt_existing_on_create = true
|
||||
|
||||
# suspend_on_destroy = true (по умолчанию) — при destroy инстанс уходит в Suspend, не удаляется.
|
||||
suspend_on_destroy = true
|
||||
}
|
||||
|
||||
# ─── Outputs ─────────────────────────────────────────────────────────────────
|
||||
|
||||
output "dev_org_id" {
|
||||
description = "ID созданной организации (используется в зависимых ресурсах)"
|
||||
value = nubes_vc_org.dev_org.id
|
||||
}
|
||||
|
||||
output "dev_org_state_flat" {
|
||||
description = "Плоский state организации — endpoints, статусы"
|
||||
value = nubes_vc_org.dev_org.state_out_flat
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
// Создано: 2026-03-23
|
||||
// main.tf — провайдер Nubes + переменные для примера NODEJS.
|
||||
// Ресурс nubes_nodejs: managed Node.js приложение в облаке (не sless-функция).
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "terra.k8c.ru/nubes/nubes"
|
||||
version = "5.0.19"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "api_token" {
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "realm" {
|
||||
type = string
|
||||
description = "resource_realm — зона размещения ресурса (например: k8s-3-sandbox-nubes-ru)"
|
||||
}
|
||||
|
||||
variable "git_path" {
|
||||
type = string
|
||||
description = "URL git-репозитория с кодом приложения"
|
||||
}
|
||||
|
||||
provider "nubes" {
|
||||
api_token = var.api_token
|
||||
api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm"
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
# Создано: 2026-03-23
|
||||
# nodejs.tf — ресурс nubes_nodejs: managed Node.js приложение.
|
||||
# Параметры взяты из документации terra.k8c.ru/docs/nubes/nubes/5.0.19/30_registry/resources/nodejs_params_create/
|
||||
|
||||
resource "nubes_nodejs" "app" {
|
||||
resource_name = "nodejsdemo1"
|
||||
domain = "domma"
|
||||
resource_realm = var.realm
|
||||
git_path = var.git_path
|
||||
app_version = "23"
|
||||
resource_c_p_u = 500
|
||||
resource_memory = 1024
|
||||
resource_instances = 1
|
||||
json_env = jsonencode({})
|
||||
adopt_existing_on_create = true
|
||||
# health_path не задан — используется дефолтный /
|
||||
}
|
||||
|
||||
output "nodejs_domain" {
|
||||
description = "Домен развёрнутого Node.js приложения"
|
||||
value = nubes_nodejs.app.domain
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
# POSTGRES — Пример: Serverless-функции с Managed PostgreSQL
|
||||
|
||||
Демонстрирует интеграцию sless (serverless functions) с управляемым PostgreSQL (nubes_postgres).
|
||||
|
||||
## Что делает этот пример
|
||||
|
||||
1. **Создаёт Managed PostgreSQL** через Terraform (nubes_postgres + nubes_postgres_user + nubes_postgres_database)
|
||||
2. **Инициализирует БД**: одноразовый `sless_job` создаёт таблицу `terraform_demo_table`
|
||||
3. **Запускает 3 HTTP-сервиса**:
|
||||
- `pg-info` (Node.js 20) — версия PostgreSQL-сервера + количество строк в таблице
|
||||
- `pg-table-reader` (Python 3.11) — чтение всех строк из таблицы
|
||||
- `pg-table-writer` (Python 3.11) — добавление новой строки
|
||||
|
||||
## Структура файлов
|
||||
|
||||
```
|
||||
POSTGRES/
|
||||
├── main.tf # terraform + провайдеры (sless, nubes_cloud)
|
||||
├── postgres.tf # Managed PostgreSQL: DB, пользователь, locals с credentials
|
||||
├── resources.tf # Namespace и сетевые ресурсы
|
||||
├── functions.tf # sless_job (init) + 3 x sless_service
|
||||
├── terraform.tfvars # Переменные: realm, s3_uid, token
|
||||
├── stress_test.sh # Стресс-тест функций (не трогает PG lifecycle)
|
||||
├── stress_destroy_apply.sh.disabled # ОТКЛЮЧЁН — стресс-тест PG lifecycle
|
||||
├── code/
|
||||
│ ├── sql-runner/ # Python: одноразовое выполнение SQL (CREATE TABLE)
|
||||
│ ├── pg-info/ # Node.js: версия PG + строки
|
||||
│ ├── table-rw/ # Python: list_rows + add_row
|
||||
│ ├── pg-stats/ # Python: расширенная статистика PG
|
||||
│ ├── funcs-list/ # Утилита: листинг функций
|
||||
│ └── stress-*/ # Функции для стресс-тестирования
|
||||
└── scripts/ # Вспомогательные скрипты
|
||||
```
|
||||
|
||||
## Как запустить
|
||||
|
||||
### Предварительные требования
|
||||
|
||||
- Terraform >= 1.3
|
||||
- Токен sless: `SLESS_TOKEN` (или в `terraform.tfvars`)
|
||||
- Токен nubes_cloud: `NUBES_TOKEN`
|
||||
- Доступ к realm (например, `ffd1f598c169b0ae`)
|
||||
|
||||
### Запуск
|
||||
|
||||
```bash
|
||||
# 1. Инициализация
|
||||
terraform init
|
||||
|
||||
# 2. Проверка плана
|
||||
terraform plan
|
||||
|
||||
# 3. Применение (создаст PG + сервисы, запустит init job)
|
||||
terraform apply
|
||||
```
|
||||
|
||||
> Первый `apply` может занять 10–15 минут: создание PG-инстанса + kaniko-сборка образов.
|
||||
|
||||
### Переменные (`terraform.tfvars`)
|
||||
|
||||
```hcl
|
||||
realm = "ffd1f598c169b0ae" # Реалм (namespace в sless)
|
||||
s3_uid = "s01234" # S3 bucket для nubes_postgres бэкапов
|
||||
sless_token = "..." # Bearer-токен для sless API
|
||||
nubes_token = "..." # Bearer-токен для nubes_cloud API
|
||||
```
|
||||
|
||||
### Вывод после apply
|
||||
|
||||
```
|
||||
Outputs:
|
||||
table_reader_url = "https://sless.kube5s.ru/v1/namespaces/.../services/pg-table-reader/invoke"
|
||||
table_writer_url = "https://sless.kube5s.ru/v1/namespaces/.../services/pg-table-writer/invoke"
|
||||
```
|
||||
|
||||
### Вызов функций
|
||||
|
||||
```bash
|
||||
# Информация о PG (Node.js)
|
||||
curl https://.../services/pg-info/invoke
|
||||
|
||||
# Список строк таблицы (Python)
|
||||
curl https://.../services/pg-table-reader/invoke
|
||||
|
||||
# Добавить строку (Python)
|
||||
curl -X POST https://.../services/pg-table-writer/invoke \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"title": "Hello from sless!"}'
|
||||
```
|
||||
|
||||
## Стресс-тест
|
||||
|
||||
`stress_test.sh` — нагружает функции HTTP-запросами. Запускать после `terraform apply`:
|
||||
|
||||
```bash
|
||||
./stress_test.sh
|
||||
```
|
||||
|
||||
> `stress_destroy_apply.sh.disabled` — ранний тест PG lifecycle (destroy+apply цикл).
|
||||
> **Отключён** из-за проблем с удалением postgres_user в определённых сценариях.
|
||||
@ -1,9 +0,0 @@
|
||||
// Создано: 2026-04-10
|
||||
// Демо-функция: возвращает текущее время сервера.
|
||||
// Юзер меняет код под себя и перебилдит через terraform apply.
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports.handler = function handler(event) {
|
||||
return `Текущее время: ${new Date().toISOString()}`;
|
||||
};
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"dependencies": {}
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
# Создано: 2026-04-10
|
||||
# Изменено: 2026-03-23 — упрощён до поля ввода выражения (демонстрация деплоя).
|
||||
# Принимает произвольное математическое выражение: "2+2*(3-1)", "(10/3)**2" и т.д.
|
||||
# GET → HTML страница с формой; POST с {expr} → вычисление через безопасный eval.
|
||||
# Безопасность eval: __builtins__=None, только math-функции в locals.
|
||||
|
||||
import math
|
||||
|
||||
_PAGE = """<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Калькулятор — Python 3.11</title>
|
||||
<style>
|
||||
body { font-family: monospace; background: #0f172a; color: #e2e8f0;
|
||||
display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; }
|
||||
.box { background: #1e293b; border-radius: 12px; padding: 32px; width: 420px; box-shadow: 0 8px 32px #0005; }
|
||||
h2 { margin: 0 0 4px; font-size: 20px; color: #7dd3fc; }
|
||||
.sub { color: #475569; font-size: 12px; margin-bottom: 24px; }
|
||||
input { width: 100%; box-sizing: border-box; padding: 10px 14px; font-size: 18px; font-family: monospace;
|
||||
background: #0f172a; border: 1px solid #334155; border-radius: 8px; color: #f1f5f9; outline: none; }
|
||||
input:focus { border-color: #38bdf8; }
|
||||
button { margin-top: 12px; width: 100%; padding: 12px; font-size: 16px; background: #0369a1;
|
||||
color: #fff; border: none; border-radius: 8px; cursor: pointer; }
|
||||
button:hover { background: #0284c7; }
|
||||
button:disabled { background: #1e3a5f; color: #475569; cursor: default; }
|
||||
.result { margin-top: 20px; padding: 14px; border-radius: 8px; font-size: 22px; text-align: center; display: none; }
|
||||
.ok { background: #064e3b; color: #6ee7b7; display: block; }
|
||||
.err { background: #450a0a; color: #fca5a5; font-size: 14px; display: block; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<h2>Калькулятор</h2>
|
||||
<div class="sub">Python 3.11 · runtime: sless</div>
|
||||
<input id="expr" autofocus placeholder="например: 2 + 2 * (3 - 1)">
|
||||
<button id="btn" onclick="calc()">Вычислить</button>
|
||||
<div id="result" class="result"></div>
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById('expr').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') calc();
|
||||
});
|
||||
async function calc() {
|
||||
const expr = document.getElementById('expr').value.trim();
|
||||
if (!expr) return;
|
||||
const btn = document.getElementById('btn');
|
||||
const res = document.getElementById('result');
|
||||
btn.disabled = true;
|
||||
btn.textContent = '…';
|
||||
try {
|
||||
const r = await fetch('', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({expr: expr})
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.error) {
|
||||
res.className = 'result err';
|
||||
res.textContent = data.error;
|
||||
} else {
|
||||
res.className = 'result ok';
|
||||
res.textContent = expr + ' = ' + data.result;
|
||||
}
|
||||
} catch(e) {
|
||||
res.className = 'result err';
|
||||
res.textContent = 'Ошибка сети: ' + e.message;
|
||||
}
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Вычислить';
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# Разрешённые math-функции в eval — без __builtins__ нет доступа к exec/open/etc.
|
||||
_MATH_LOCALS = {k: getattr(math, k) for k in dir(math) if not k.startswith('_')}
|
||||
|
||||
|
||||
def handler(event):
|
||||
if event.get('_method') == 'POST':
|
||||
expr = str(event.get('expr', '')).strip()
|
||||
return _compute(expr)
|
||||
# GET → HTML страница
|
||||
return _PAGE
|
||||
|
||||
|
||||
def _compute(expr):
|
||||
if not expr:
|
||||
return {'error': 'Введите выражение'}
|
||||
try:
|
||||
result = eval(expr, {'__builtins__': None}, _MATH_LOCALS) # noqa: S307
|
||||
if not isinstance(result, (int, float)):
|
||||
return {'error': 'Результат не является числом'}
|
||||
return {'expr': expr, 'result': result}
|
||||
except ZeroDivisionError:
|
||||
return {'error': 'Деление на ноль'}
|
||||
except Exception as exc:
|
||||
return {'error': f'Ошибка: {exc}'}
|
||||
|
||||
|
||||
def _esc(s):
|
||||
# Экранируем HTML-спецсимволы — безопасный вывод в атрибут и тело.
|
||||
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||
@ -1 +0,0 @@
|
||||
# нет внешних зависимостей
|
||||
@ -1,58 +0,0 @@
|
||||
// 2026-03-21 — js-idempotent: INSERT с проверкой по idempotency_key.
|
||||
// Повторный вызов с тем же key НЕ создаёт дубль — возвращает существующую запись.
|
||||
// Тестирует: идемпотентность через SELECT ... FOR UPDATE + условный INSERT.
|
||||
const { Client } = require('pg');
|
||||
|
||||
async function run(event) {
|
||||
const key = String(event.idempotency_key ?? `auto-${Date.now()}`).slice(0, 200);
|
||||
const title = String(event.title ?? key).slice(0, 255);
|
||||
|
||||
const client = new Client({
|
||||
host: process.env.PGHOST,
|
||||
port: parseInt(process.env.PGPORT ?? '5432'),
|
||||
database: process.env.PGDATABASE,
|
||||
user: process.env.PGUSER,
|
||||
password: process.env.PGPASSWORD,
|
||||
ssl: { rejectUnauthorized: false },
|
||||
});
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
||||
// Ищем существующую запись по title (используем как idempotency key)
|
||||
const existing = await client.query(
|
||||
'SELECT id, title, created_at FROM terraform_demo_table WHERE title = $1 LIMIT 1 FOR UPDATE',
|
||||
[key]
|
||||
);
|
||||
|
||||
let action, row;
|
||||
if (existing.rows.length > 0) {
|
||||
action = 'existing';
|
||||
row = existing.rows[0];
|
||||
} else {
|
||||
const ins = await client.query(
|
||||
'INSERT INTO terraform_demo_table (title) VALUES ($1) RETURNING id, title, created_at',
|
||||
[key]
|
||||
);
|
||||
action = 'created';
|
||||
row = ins.rows[0];
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
return {
|
||||
action,
|
||||
id: row.id,
|
||||
title: row.title,
|
||||
created_at: row.created_at,
|
||||
idempotency_key: key,
|
||||
};
|
||||
} catch (e) {
|
||||
await client.query('ROLLBACK');
|
||||
throw e;
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { run };
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"name": "js-idempotent",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"pg": "^8.11.3"
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
# 2026-03-21 — pg-counter: считает строки по prefix, возвращает статистику.
|
||||
# Тестирует: SELECT COUNT с WHERE LIKE, агрегация, concurrent reads.
|
||||
import os, psycopg2
|
||||
|
||||
def count(event):
|
||||
prefix = event.get("prefix", "")
|
||||
conn = psycopg2.connect(
|
||||
host=os.environ["PGHOST"], port=int(os.environ.get("PGPORT", 5432)),
|
||||
dbname=os.environ["PGDATABASE"], user=os.environ["PGUSER"],
|
||||
password=os.environ["PGPASSWORD"], sslmode=os.environ.get("PGSSLMODE", "require"),
|
||||
)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
if prefix:
|
||||
cur.execute("SELECT COUNT(*) FROM terraform_demo_table WHERE title LIKE %s", (f"{prefix}%",))
|
||||
else:
|
||||
cur.execute("SELECT COUNT(*) FROM terraform_demo_table")
|
||||
total = cur.fetchone()[0]
|
||||
cur.execute("SELECT COUNT(*) FROM terraform_demo_table WHERE created_at > now() - interval '1 hour'")
|
||||
last_hour = cur.fetchone()[0]
|
||||
return {"total": total, "last_hour": last_hour, "prefix": prefix or "*"}
|
||||
finally:
|
||||
conn.close()
|
||||
@ -1 +0,0 @@
|
||||
psycopg2-binary
|
||||
@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "pg-info",
|
||||
"version": "1.0.0",
|
||||
"description": "sless nodejs20 function: pg version + table info",
|
||||
"dependencies": {
|
||||
"pg": "8.11.0"
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
// 2026-03-18
|
||||
// pg_info.js — NodeJS-функция: проверка работы JS runtime + чтение мета-данных БД.
|
||||
// Подключается к PostgreSQL через пакет pg, возвращает версию сервера и счётчик строк.
|
||||
// Демонстрирует: nodejs20 runtime, npm-зависимость (package.json), PG из JS.
|
||||
//
|
||||
// ENV (те же что у python-функций):
|
||||
// PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD, PGSSLMODE
|
||||
//
|
||||
// Entrypoint: pg_info.info
|
||||
|
||||
'use strict';
|
||||
|
||||
const { Client } = require('pg');
|
||||
|
||||
exports.info = async (event) => {
|
||||
const client = new Client({
|
||||
host: process.env.PGHOST,
|
||||
port: parseInt(process.env.PGPORT || '5432'),
|
||||
database: process.env.PGDATABASE,
|
||||
user: process.env.PGUSER,
|
||||
password: process.env.PGPASSWORD,
|
||||
// pg-пакет требует явного ssl-объекта; rejectUnauthorized: false — т.к.
|
||||
// self-signed cert на nubes managed PG, но канал всё равно шифруется.
|
||||
ssl: process.env.PGSSLMODE === 'require' ? { rejectUnauthorized: false } : false,
|
||||
});
|
||||
|
||||
await client.connect();
|
||||
try {
|
||||
const [versionRes, countRes] = await Promise.all([
|
||||
client.query('SELECT version() AS v'),
|
||||
client.query('SELECT COUNT(*) AS cnt FROM terraform_demo_table'),
|
||||
]);
|
||||
|
||||
return {
|
||||
runtime: 'nodejs20',
|
||||
node_version: process.version,
|
||||
pg_version: versionRes.rows[0].v,
|
||||
table_rows: parseInt(countRes.rows[0].cnt, 10),
|
||||
code_version: 'v2-agent-test',
|
||||
};
|
||||
} finally {
|
||||
await client.end();
|
||||
}
|
||||
};
|
||||
@ -1,38 +0,0 @@
|
||||
# 2026-03-19
|
||||
# pg_stats.py — тестовая функция (Test 7): возвращает агрегированную статистику
|
||||
# по таблице terraform_demo_table: кол-во строк, дата первой и последней записи.
|
||||
# Создаётся и удаляется в рамках тестового прогона.
|
||||
#
|
||||
# Entrypoint: pg_stats.get_stats
|
||||
|
||||
import os
|
||||
import psycopg2
|
||||
import json
|
||||
|
||||
_CODE_VERSION = "v1-test7"
|
||||
|
||||
|
||||
def get_stats(event):
|
||||
conn = psycopg2.connect(
|
||||
host=os.environ["PGHOST"],
|
||||
port=int(os.environ.get("PGPORT", "5432")),
|
||||
dbname=os.environ["PGDATABASE"],
|
||||
user=os.environ["PGUSER"],
|
||||
password=os.environ["PGPASSWORD"],
|
||||
sslmode=os.environ.get("PGSSLMODE", "require"),
|
||||
)
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT COUNT(*) AS cnt, MIN(created_at) AS first, MAX(created_at) AS last "
|
||||
"FROM terraform_demo_table"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
return {
|
||||
"version": _CODE_VERSION,
|
||||
"total_rows": row[0],
|
||||
"first_row_at": str(row[1]) if row[1] else None,
|
||||
"last_row_at": str(row[2]) if row[2] else None,
|
||||
}
|
||||
finally:
|
||||
conn.close()
|
||||
@ -1 +0,0 @@
|
||||
psycopg2-binary==2.9.9
|
||||
@ -1,36 +0,0 @@
|
||||
# Создано: 2026-04-10
|
||||
# functions.tf — sless_service ресурсы для примера POSTGRES.
|
||||
# Здесь: два калькуляторa — Python и Node.js.
|
||||
# sless_service = long-running Deployment + постоянный URL (в отличие от sless_function).
|
||||
|
||||
# ─── Python-калькулятор ──────────────────────────────────────────────────────
|
||||
|
||||
resource "sless_service" "calc_python" {
|
||||
name = "calc-python"
|
||||
runtime = "python3.11"
|
||||
entrypoint = "handler.handler"
|
||||
memory_mb = 128
|
||||
timeout_sec = 30
|
||||
source_dir = "${path.module}/code/calc-python"
|
||||
}
|
||||
|
||||
output "calc_python_url" {
|
||||
description = "URL Python-калькулятора"
|
||||
value = sless_service.calc_python.url
|
||||
}
|
||||
|
||||
# ─── Node.js-калькулятор ─────────────────────────────────────────────────────
|
||||
|
||||
resource "sless_service" "calc_node" {
|
||||
name = "calc-node"
|
||||
runtime = "nodejs20"
|
||||
entrypoint = "handler.handler"
|
||||
memory_mb = 128
|
||||
timeout_sec = 30
|
||||
source_dir = "${path.module}/code/calc-node"
|
||||
}
|
||||
|
||||
output "calc_node_url" {
|
||||
description = "URL Node.js-калькулятора"
|
||||
value = sless_service.calc_node.url
|
||||
}
|
||||
@ -1,64 +0,0 @@
|
||||
// 2026-03-17 17:05
|
||||
// main.tf — провайдеры и переменные для Nubes + sless.
|
||||
terraform {
|
||||
required_providers {
|
||||
nubes = {
|
||||
source = "terra.k8c.ru/nubes/nubes"
|
||||
version = "5.0.31"
|
||||
}
|
||||
sless = {
|
||||
source = "terra.k8c.ru/naeel/sless"
|
||||
version = "~> 0.1.19"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "api_token" {
|
||||
type = string
|
||||
sensitive = true
|
||||
description = "Nubes API token"
|
||||
}
|
||||
variable "s3_uid" {
|
||||
type = string
|
||||
sensitive = true
|
||||
description = "Nubes S3 UID"
|
||||
}
|
||||
variable "realm" {
|
||||
type = string
|
||||
sensitive = true
|
||||
description = "resource_realm parameter for nubes_postgres resource"
|
||||
}
|
||||
|
||||
// 2026-03-18 — pg_user/pg_password помечены optional (default="") для сверки.
|
||||
// Реальные credentials берутся из vault_secrets через locals в resources.tf.
|
||||
variable "pg_user" {
|
||||
type = string
|
||||
sensitive = true
|
||||
default = ""
|
||||
description = "Только для сверки. Реальный username из nubes_postgres_user.pg_user.username. Должен совпадать с vault."
|
||||
}
|
||||
|
||||
variable "pg_password" {
|
||||
type = string
|
||||
sensitive = true
|
||||
default = ""
|
||||
description = "Только для сверки. Реальный пароль из vault_secrets. Должен совпадать с tfvars."
|
||||
}
|
||||
|
||||
# Nubes endpoints — не путать:
|
||||
# API Dashboard (для Terraform-провайдеров): https://deck-api-test.ngcloud.ru/api/v1/index.cfm
|
||||
# UI облака (только браузер, не для кода): https://deck-test.ngcloud.ru/
|
||||
# ВАЖНО: nubes и sless провайдеры требуют API endpoint, НЕ UI!
|
||||
|
||||
provider "nubes" {
|
||||
api_token = var.api_token
|
||||
api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm"
|
||||
}
|
||||
|
||||
provider "sless" {
|
||||
endpoint = "https://sless.kube5s.ru"
|
||||
token = var.api_token
|
||||
nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
|
||||
}
|
||||
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
// 2026-03-20 — выделено из resources.tf: только managed PostgreSQL ресурсы.
|
||||
|
||||
# Актуальные credentials из vault_secrets (authoritatively) — vault синхронизирован с кластером.
|
||||
# Структура vault_secrets["users"]: JSON-строка {"username": {"password": "...", "username": "..."}}
|
||||
|
||||
locals {
|
||||
# try() нужен: vault_secrets["users"] появляется только ПОСЛЕ создания первого пользователя.
|
||||
# На первом apply ключа ещё нет → пустая map. Пароль подтянется при следующем apply.
|
||||
pg_creds_map = try(jsondecode(lookup(nubes_postgres.npg.vault_secrets, "users", "{}")), {})
|
||||
pg_username = nubes_postgres_user.pg_user.username
|
||||
pg_password = try(local.pg_creds_map[local.pg_username]["password"], "")
|
||||
pg_host = nubes_postgres.npg.state_out_flat["internalConnect.master"]
|
||||
pg_database = nubes_postgres_database.db.db_name
|
||||
}
|
||||
|
||||
|
||||
resource "nubes_postgres" "npg" {
|
||||
resource_name = "pg-sless-demo"
|
||||
# s3_uid = "s01325"
|
||||
s3_uid = var.s3_uid
|
||||
resource_realm = var.realm
|
||||
resource_instances = 1
|
||||
resource_memory = 512
|
||||
resource_c_p_u = 500
|
||||
resource_disk = "1"
|
||||
app_version = "17"
|
||||
json_parameters = jsonencode({
|
||||
log_connections = "off"
|
||||
log_disconnections = "off"
|
||||
})
|
||||
enable_pg_pooler_master = false
|
||||
enable_pg_pooler_slave = false
|
||||
allow_no_s_s_l = false
|
||||
auto_scale = false
|
||||
auto_scale_percentage = 10
|
||||
auto_scale_tech_window = 0
|
||||
auto_scale_quota_gb = "1"
|
||||
need_external_address_master = false
|
||||
|
||||
# suspend_on_destroy = false
|
||||
operation_timeout = "11m"
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
|
||||
resource "nubes_postgres_user" "pg_user" {
|
||||
postgres_id = nubes_postgres.npg.id
|
||||
username = "user0"
|
||||
role = "ddl_user"
|
||||
adopt_existing_on_create = true
|
||||
}
|
||||
|
||||
resource "nubes_postgres_database" "db" {
|
||||
postgres_id = nubes_postgres.npg.id
|
||||
db_name = "db0"
|
||||
db_owner = nubes_postgres_user.pg_user.username
|
||||
adopt_existing_on_create = true
|
||||
# suspend_on_destroy = false
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
// 2026-03-20 — содержимое перенесено в два файла:
|
||||
// postgres.tf — managed PostgreSQL ресурсы (nubes_postgres, user, database, locals)
|
||||
// functions.tf — sless функции, сервисы, джобы, outputs
|
||||
64
README.md
64
README.md
@ -1,75 +1,55 @@
|
||||
# Примеры использования sless
|
||||
# sless — примеры
|
||||
|
||||
## Обзор платформы
|
||||
|
||||
**sless** — система управления serverless-функциями на базе Kubernetes. Разработчик загружает код функции, платформа собирает из него Docker-образ, разворачивает его в кластере и предоставляет HTTP-эндпоинт для вызова. Всё описывается декларативно через Terraform.
|
||||
|
||||
### Основные ресурсы провайдера
|
||||
|
||||
| Ресурс | Назначение |
|
||||
|---|---|
|
||||
| `sless_service` | Long-running HTTP-сервис: всегда активен, отвечает на запросы. Имеет свой URL после деплоя. |
|
||||
| `sless_job` | Одноразовый запуск функции: собирает образ, выполняет код, завершается. Используется для миграций БД, batch-обработки и т.д. |
|
||||
|
||||
Namespace функций вычисляется автоматически из JWT-токена: `sless-{sha256[:8]}`.
|
||||
**sless** — платформа для запуска serverless-функций на базе Kubernetes.
|
||||
Разработчик загружает код, платформа собирает Docker-образ и разворачивает его в кластере.
|
||||
Всё описывается декларативно через Terraform.
|
||||
|
||||
---
|
||||
|
||||
## Требования
|
||||
## Ресурсы Terraform-провайдера
|
||||
|
||||
- Terraform >= 1.3
|
||||
- JWT-токен для аутентификации в sless API
|
||||
- JWT-токен для Nubes Cloud API (если используются managed-ресурсы: PostgreSQL и т.д.)
|
||||
- Доступ к `https://sless.kube5s.ru`
|
||||
| Ресурс | Что делает |
|
||||
|---|---|
|
||||
| `sless_service` | HTTP-сервис: всегда запущен, отвечает на запросы, имеет постоянный URL |
|
||||
| `sless_job` | Разовый запуск: выполняет код один раз и завершается (установка ПО, миграции и т.д.) |
|
||||
|
||||
---
|
||||
|
||||
## Конфигурация провайдера
|
||||
|
||||
```hcl
|
||||
provider "sless" {
|
||||
endpoint = "https://sless.kube5s.ru"
|
||||
token = var.sless_token
|
||||
}
|
||||
|
||||
provider "nubes_cloud" {
|
||||
base_url = "https://deck-api-test.ngcloud.ru/api/v1"
|
||||
token = var.nubes_token
|
||||
token = var.api_token
|
||||
}
|
||||
```
|
||||
|
||||
> Токены задаются в `terraform.tfvars` — этот файл добавлен в `.gitignore`.
|
||||
Токен задаётся в `terraform.tfvars` (файл в `.gitignore`, не попадает в git).
|
||||
|
||||
---
|
||||
|
||||
## Примеры
|
||||
|
||||
### `POSTGRES` — Serverless-функции с Managed PostgreSQL
|
||||
### [`VM/`](VM/) — Виртуальная машина в Nubes vDC
|
||||
|
||||
Полный пример: managed PostgreSQL + одноразовый init-job + 3 HTTP-сервиса (чтение/запись данных и информация о PG).
|
||||
Создаёт vApp + Ubuntu 22.04 VM в облаке Nubes. После создания — автоматически устанавливает ПО (nginx, Docker, пакеты) через serverless-джобы по SSH.
|
||||
|
||||
Языки: Python 3.11, Node.js 20.
|
||||
|
||||
```bash
|
||||
cd POSTGRES
|
||||
terraform init
|
||||
terraform apply
|
||||
```
|
||||
|
||||
Подробности: [POSTGRES/README.md](POSTGRES/README.md)
|
||||
**→ [Начать здесь](VM/README.md)**
|
||||
|
||||
---
|
||||
|
||||
## Полезные команды
|
||||
|
||||
```bash
|
||||
# Посмотреть состояние задеплоенных ресурсов:
|
||||
# Посмотреть состояние ресурсов:
|
||||
terraform show
|
||||
|
||||
# Принудительно пересобрать сервис (после изменения кода):
|
||||
terraform apply -replace=sless_service.<имя>
|
||||
|
||||
# Повторно запустить job: увеличить run_id в .tf-файле, затем:
|
||||
# Повторно запустить job: увеличить run_id в terraform.tfvars, затем:
|
||||
terraform apply
|
||||
|
||||
# Удалить все ресурсы примера:
|
||||
# Принудительно пересобрать сервис после изменения кода:
|
||||
terraform apply -replace=sless_service.<имя>
|
||||
|
||||
# Удалить все ресурсы:
|
||||
terraform destroy
|
||||
```
|
||||
|
||||
Loading…
Reference in New Issue
Block a user