Files
admin/.wiki/concepts/emspb-vds-deploy-runbook.md
vitya fd48df6225 feat(emspb): v3 cutover LIVE — snolla 0.42.1→0.43.2 (aws-sdk v2→v3)
5-й сайт тиража v3 (5/6 GREEN). In-place bump стека 18 (DNS на VDS с
2026-07-02). Commit victor/emspb.ru 5afd8a1 (bump + resolutions + yarn.lock
реген). Build на VDS → registry.kzntsv.site/emspb:5afd8a1 digest 72bb59fc.
Staging :5082 parity-gate GREEN (29/29 routes 200, theme assets 7/7 md5
IDENTICAL — v3 S3-direct read из vds MinIO 2025). Portainer PUT stack 18
env-preserving (8/8, node in-memory put-stack), prune:false pullImage:true.
Live smoke GREEN. Rollback = 95a5c42 (v2).

- emspb.compose.yml: 95a5c42→5afd8a1
- emspb-vds-deploy-runbook: §0.43.2
- minio-split-cutover-runbook: progress-table emspb , остался tandemmebel
- NEXT_SESSION: 5/6 GREEN, остался tandemmebel (стек 20, был BLOCKED на
  sharp-staging дефекте — проверить перед бампом)

Остался 1 сайт: tandemmebel.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-30 17:46:19 +03:00

14 KiB
Raw Blame History

title: emspb.ru snolla-app — VDS deploy runbook (stack / traefik / env / cutover / rollback) type: concept tags: [emspb, snolla, vds, deploy, docker, portainer, traefik, runbook, migration] related: ../entities/vds-kzntsv, ../entities/ruvds-iis-host, portainer-stack-management-vds, minio-imgproxy-on-vds, labtools-vds-deploy-runbook, snolla-live-prod-inplace-image-bump updated: 2026-07-05

emspb.ru → VDS deploy runbook

Вынос emspb.ru (snolla-приложение, @snollajs/snolla 0.28.4, server-side Liquid) с ../entities/ruvds-iis-host (catch-all CMS) в отдельный docker-контейнер на ../entities/vds-kzntsv (89.253.255.94), за traefik. Модель — snolla-app (НЕ pilorama98.ru/Nuxt-contentApi): читает БД-контент MoreThenCms (mssql.kzntsv.site)

  • ассеты из MinIO (minio.kzntsv.site) через imgproxy. Зеркало labtools-vds-deploy-runbook — тот же паттерн.

Артефакты

  • Код: victor/emspb.ru @ b6e361a (apps/web, ре-ревью PASS, 29/29 parity, snolla 0.28.4 final). deploy/Dockerfile (multi-stage node:22-slim, non-root, healthcheck /robots.txt).
  • Образ: registry.kzntsv.site/emspb:b6e361a (+:latest). Собран НА VDS (обход traefik-499). digest sha256:8f5ba02651b71f340fa9bc3b079fd2853b01b1461e6c479fb2cf788691d31a74.
  • Стек Portainer: emspb (Id 18, endpoint 1). Source-of-truth compose: admin/host-stacks/vds-kzntsv/emspb.compose.yml.
  • siteId: 96EBC481-D26A-47BE-B660-13D49E7D0A61, activeTheme DD8D6F7A-CEDC-4D5A-8BFB-E5F9BE9DC035 (theme store MinIO themes/dd8d6f7acedc4d5a8bfbe5f9be9dc035/). Non-secret — в production.json, НЕ env.

Сборка образа (на VDS)

# чистый git-archive (только tracked → без config/default.json и node_modules) на VDS
git -C ~/projects/emspb.ru archive --format=tar b6e361a \
  | ssh vitya@89.253.255.94 'rm -rf ~/build/emspb && mkdir -p ~/build/emspb && tar -x -C ~/build/emspb'
# build с build-arg VERDACCIO_TOKEN (pass vds-kzntsv/full-env VERDACCIO_CI_TOKEN), передаётся через env (не в argv), push из VDS
ssh vitya@89.253.255.94 "cd ~/build/emspb && VERDACCIO_TOKEN='<token>' docker build -f deploy/Dockerfile \
  --build-arg VERDACCIO_TOKEN -t registry.kzntsv.site/emspb:b6e361a -t registry.kzntsv.site/emspb:latest . \
  && docker push registry.kzntsv.site/emspb:b6e361a && docker push registry.kzntsv.site/emspb:latest"
  • VERDACCIO_TOKEN — build-time only (discarded build-стейдж; в финальный образ не попадает; docker linter warn-only ×2 — ожидаемо).
  • config/default.json (dev-секреты) исключён .dockerignore — в образе только production.json + custom-environment-variables.json (guard проверен на архиве: default.json ABSENT).

Runtime env-контракт (8 секретов — Portainer stack-env, НЕ в образ/git)

Маппинг env→config: apps/web/config/custom-environment-variables.json. Non-secret (host/siteId/endpoints) — в production.json. Значения идентичны labtools (все snolla-тенанты читают одну MoreThenCms + общий MinIO/imgproxy/smtp; tenant-разница = siteId, не секрет). При создании стека env-массив переиспользован verbatim из labtools stack (Id 17) через Portainer API.

ENV Значение Источник
DB_USER snolla web.config боя MoreThenCmsEntities
DB_PASSWORD ⟨секрет⟩ web.config боя MoreThenCmsEntities (User Id=snolla)
IMGPROXY_KEY ⟨128hex⟩ pass minio-vds/full-env (== сервер imgproxy books-vds — HMAC совпадает)
IMGPROXY_SALT ⟨128hex⟩ pass minio-vds/full-env
S3_ACCESS_KEY_ID ⟨секрет⟩ pass minio-vds/full-env (MINIO_ROOT_USER)
S3_SECRET_ACCESS_KEY ⟨секрет⟩ pass minio-vds/full-env (MINIO_ROOT_PASSWORD)
SMTP_USER noreply@snolla.com pass snolla-smtp/full-env
SMTP_PASSWORD ⟨секрет⟩ pass snolla-smtp/full-env (SMTP_PASS)

PORT — не переопределять (образ дефолтит 5000; EXPOSE 5000; healthcheck /robots.txt на $PORT). traefik service-port = 5000.

Создание стека (Portainer API, на VDS — bash+curl+jq, обходит PS-кириллица-гочу)

# JWT (pass vds-kzntsv/full-env PORTAINER_PASS); env переиспользован из labtools stack 17
JWT=$(curl -ksS -X POST https://portainer.vds.kzntsv.site/api/auth -d '{"username":"vitya","password":"<pw>"}' | jq -r .jwt)
ENV=$(curl -ksS -H "Authorization: Bearer $JWT" https://portainer.vds.kzntsv.site/api/stacks/17 | jq '.Env')
COMPOSE=$(cat ~/build/emspb.compose.yml)
PAYLOAD=$(jq -n --arg name emspb --arg compose "$COMPOSE" --argjson env "$ENV" '{name:$name,stackFileContent:$compose,env:$env,fromAppTemplate:false}')
curl -ksS -X POST "https://portainer.vds.kzntsv.site/api/stacks/create/standalone/string?endpointId=1" \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" --data "$PAYLOAD"   # → Id=18

Предусловие pull: registry.kzntsv.site зарегистрирован в Portainer как Custom registry Id 1 (иначе no basic auth).

Runtime egress (наружу из proxy-сети)

mssql.kzntsv.site:1433 (БД-контент, обязателен) · minio.kzntsv.site:443 (ассеты) · imgproxy.kzntsv.site:443 · smtp.yandex.ru:465 (формы).

Staging smoke (2026-07-02, GREEN) — emspb.vds.kzntsv.site vs бой www.emspb.ru (RUVDS IIS 80.64.31.36, через --resolve)

  • Status-паритет 23/23: 5 nav (/ /contacts /portfolio /prices) + 17 service-детальных (/ustanovka-*, /almaznoe-burenie-* …) все 200/200; /services 404/404 (nav-якорь, не страница); /index.php 404/404; /robots.txt 200 байт-в-байт (31 B).
  • Дельта размера контент-страниц стабильна +8 B, полностью объяснена: +9 host-строка в canonical/og itemprop (emspb.vds.kzntsv.site на 9 символов длиннее www.emspb.ru), 1 — новый snolla почистил битый двойной слэш боя /images//3.jpg/images/3.jpg (оба варианта → 200, картинка не сломана). / — байт-в-байт (canonical из DB-домена, не request-host).
  • theme CSS ×3 (framework/bootstrap/slick) из MinIO — md5 IDENTICAL с боем; Cache-Control: max-age=86400, public, must-revalidate, text/css.
  • theme image /images/3.jpg из MinIO — md5 IDENTICAL (115618 B, image/jpeg).
  • Редиректы: /contacts/→301→/contacts (trailing-slash), /Contacts→301→lowercase, /Portfolio→301→lowercase — как бой.
  • Контейнер healthy (t+10s), MSSQL (tedious) + S3 (aws-sdk) подключены, listening at http://localhost:5000, лог чист (только deprecation-warnings tedious/aws-sdk-v2).

Cutover (live DNS) — ВЫПОЛНЕН 2026-07-02

Порядок был:

  1. (оператор) reg.ru: A-записи emspb.ru + www.emspb.ru89.253.255.94 (с RUVDS 80.64.31.36). — сделал оператор (сигнал «поменял DNS у провайдера»). Проверено внешним резолвером (8.8.8.8): оба хоста → VDS; labtools остался на RUVDS (не путать домены!).
  2. (ops) В стеке emspb (Id 18) traefik-rule расширен: Host(\emspb.ru`) || Host(`www.emspb.ru`)— Portainer PUT (env сохранён, pullImage=false). LE HTTP-01 выпустил cert на первом хите после flip (~t+20-70s). **staging-хостemspb.vds.kzntsv.site` убран из rule post-cutover** (проверено: → 404). Cert перевыпустится на 2 боевых SAN при renewal.
  3. Live-smoke GREEN: www.emspb.ru+emspb.ru → 200 ssl_verify=0 (cert доверенный); все страницы 200; canonical нормализован на www.emspb.ru; live VDS /contacts == старый RUVDS байт-в-байт (host уравнялся).

⚠️ Порядок критичен: Host-правило добавлено ТОЛЬКО ПОСЛЕ flip DNS (иначе LE HTTP-01 challenge упал бы на RUVDS → сожгли бы rate-limit). Перед PUT — обязательно проверить внешним резолвером, что DNS реально на VDS.

Guard (как labtools)

  • НЕ добавлять Host(emspb.ru) в traefik-rule ДО флипа DNS — traefik на reload проактивно тянет LE-cert (HTTP-01), challenge упадёт на RUVDS → сожжём LE rate-limit.
  • НЕ выводить RUVDS IIS emspb из эксплуатации — это rollback-путь.
  • НЕ флипать DNS самим — это делает хозяин домена/оператор.

Rollback

  • DNS: вернуть A-записи emspb.ru/www80.64.31.36 (RUVDS IIS живой, нетронут). Откат = смена DNS.
  • Стек: Portainer → stack emspb (Id 18) → remove (или откат тега образа). Образ в registry остаётся.

0.42.1 in-place bump — ВЫПОЛНЕН 2026-07-05

Тираж snolla 0.42.1. emspb.ru обновлён на живом стеке 18 in-place (домен уже на VDS, DNS не трогали). Рецепт — snolla-live-prod-inplace-image-bump. Оператор дал отмашку на боевой apply.

  • Образ: registry.kzntsv.site/emspb:95a5c42 (snolla 0.28.4→0.42.1, digest d6299cf…). Собран на VDS.
  • Acceptance С VDS (throwaway-staging из env стека 18): 29 sitemap-роутов все 200 (0 non-2xx/3xx), content-not-lost 29/29 (0 потерь). Sitemap НЕ реструктурировался (29==29, в отличие от каталожного labtools.ru).
  • TLS: серт — SAN покрывает emspb.ru/www.emspb.ru/emspb.vds.kzntsv.site, CN косметически = staging-хост (curl validates sslverify=0). Это тот самый серт с cutover 07-02 (см. выше: «Cert перевыпустится на 2 боевых SAN при renewal») — swap его не тронул. При renewal можно пере-выпустить с CN=emspb.ru, опционально.
  • Swap: Portainer PUT стека 18 (env 8/8 сохранён) → healthy → live-smoke GREEN, sitemap 29 вживую.
  • Rollback: тег emspb:b6e361a (0.28.4, в registry) / стек 18 PUT назад / DNS→RUVDS.
  • Compose обновлён на 95a5c42. Таска [emspb-deploy-snolla-0-42-1] 🟢. Notify=victor/emspb.ru.

0.43.2 v3 cutover (aws-sdk v2→v3) — ВЫПОЛНЕН 2026-07-30

5-й сайт тиража v3 (после on-snolla, kupimknigi, labtools.ru, labtools.pro — 4 GREEN). In-place bump стека 18 (DNS на VDS с 2026-07-02, не трогали). Рецепт — minio-split-cutover-runbook §B3-v3.

  • Bump: @snollajs/snolla 0.42.1→0.43.2 в apps/web/package.json (commit 5afd8a1) + resolutions-блок в root package.json (verbatim labtools.ru: @aws-sdk/client-s3 3.1005.0 + @smithy chain 5.3.11) + реген yarn.lock (single @smithy/signature-v4@5.3.11 deduped). Pushed victor/emspb.ru master 218c3ae..5afd8a1. Endpoint fix 218c3ae (s3.endpoint→minio.vds.kzntsv.site) в master с 2026-07-29, но в бегущем образе 95a5c42 (v2) его не было — v3 cutover заодно перевёл S3-direct read на vds minio.
  • Build на VDS: git archive 5afd8a1~/build/emspbdocker build -f deploy/Dockerfile --build-arg VERDACCIO_TOKENregistry.kzntsv.site/emspb:5afd8a1(+latest), digest sha256:72bb59fc355d4f020fe49e1c410e6be411779ee1002d91cd4db7c3250f2c3358, EXIT=0. yarn install --immutable в Docker прошёл (yarn.lock matches, no YN0028).
  • Staging :5082 parity-gate GREEN (throwaway контейнер из env живого стека 18, 8 секретов, --network proxy):
    • robots.txt md5 940a0903… byte-identical (31 B); sitemap index→sitemap-pages-1.xml 29 locs == live 29.
    • 29/29 sitemap-роутов 200/200, 0 status diffs, content present (34-38 KB real bodies). Дельта +2 B = localhost:5082 (14) vs www.emspb.ru (13) ×2 (canonical+og:url) — benign host-context, post-cutover byte-identical.
    • theme assets 7/7 md5 IDENTICAL (v3 aws-sdk v3 S3-direct read из vds MinIO 2025): snolla-framework 5cb1031e… 7529B, snolla-bootstrap 9a56500a… 19624B, slick e604b364… 1183B, toolbox 382a2ccc… 10215B, theme e02370e3… 5640B, images/2.jpg 0898d8df… 387884B, /images/3.jpg 48562843… 115618B (== runbook 2026-07-02 baseline).
    • redirects parity: /contacts/→301→/contacts, /Contacts→301→lowercase, /Portfolio→301→lowercase, /Services→301 (target modulo host); /index.php+/services→404 parity.
  • Portainer PUT stack 18 LIVE: node in-memory put-stack (НЕ PS — глобальный fetch, compose в JS-var без /tmp round-trip; GET /stacks/18 для Env + /stacks/18/file для StackFileContent), env 8/8 preserved, prune:false pullImage:true mem_limit 512m. PUT 200. Контейнер 5afd8a1 healthy сразу (~1s).
  • Live smoke GREEN (via traefik): / 200 54615B, /contacts+/portfolio+/prices 200, /services+/index.php 404, robots md5 940a0903…, sitemap 29 locs, theme assets md5 LIVE==staging (v3 S3-direct), redirects 301 (target https://www.emspb.ru/... — byte-identical post-cutover), apex emspb.ru 200. Лог чист (tedious deprecation + emspb.ru (snolla) listening). TLS не тронут.
  • Rollback: emspb:95a5c42 (0.42.1 v2, жив в registry) — Portainer PUT назад / compose revert. b6e361a (0.28.4) — deeper rollback. DNS→RUVDS 80.64.31.36 — крайний.
  • Compose (admin source-of-truth) синхронно закоммичен 95a5c425afd8a1. 5/6 v3 сайтов GREEN. Остался: tandemmebel (стек 20 — был BLOCKED на sharp-staging визуальном дефекте, проверить перед бампом).